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 01/28] 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 000000000..7a2fb6ee9 --- /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 40a6118c0..92c340cb8 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 02/28] 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 fda7baf86..fcfbc5dbd 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 03/28] 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 0e4836acf..9ccaa60be 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 d44017dea..ff6182faf 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 50b801412..598783a48 100644 --- a/klippy/extras/force_move.py +++ b/klippy/extras/force_move.py @@ -131,8 +131,13 @@ class ForceMove: 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 ca9b8eb59..ca0756cfa 100644 --- a/klippy/extras/safe_z_home.py +++ b/klippy/extras/safe_z_home.py @@ -40,8 +40,7 @@ class SafeZHoming: 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 0c4bb9255..2715d1720 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -73,9 +73,10 @@ class CartKinematics: 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 @@ class CartKinematics: 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 d3b755001..dc80d1eec 100644 --- a/klippy/kinematics/corexy.py +++ b/klippy/kinematics/corexy.py @@ -43,9 +43,10 @@ class CoreXYKinematics: 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 @@ class CoreXYKinematics: # 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 72134c327..b1c46146b 100644 --- a/klippy/kinematics/corexz.py +++ b/klippy/kinematics/corexz.py @@ -43,9 +43,10 @@ class CoreXZKinematics: 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 @@ class CoreXZKinematics: # 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 bb81ab18a..04a75547b 100644 --- a/klippy/kinematics/delta.py +++ b/klippy/kinematics/delta.py @@ -105,6 +105,11 @@ class DeltaKinematics: 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 @@ class DeltaKinematics: 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 bb2313708..e48949af2 100644 --- a/klippy/kinematics/deltesian.py +++ b/klippy/kinematics/deltesian.py @@ -117,6 +117,10 @@ class DeltesianKinematics: 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 265a0e6da..85f20bcc6 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -75,9 +75,10 @@ class HybridCoreXYKinematics: 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 @@ class HybridCoreXYKinematics: 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 2d89e3f7b..8e5c3d92e 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -75,9 +75,10 @@ class HybridCoreXZKinematics: 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 @@ class HybridCoreXZKinematics: 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 ff3c57a9a..fda1f073a 100644 --- a/klippy/kinematics/none.py +++ b/klippy/kinematics/none.py @@ -13,6 +13,8 @@ class NoneKinematics: 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 deed26d06..fdd428a60 100644 --- a/klippy/kinematics/polar.py +++ b/klippy/kinematics/polar.py @@ -51,9 +51,12 @@ class PolarKinematics: 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 @@ class PolarKinematics: 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 1eb050baa..46a63b822 100644 --- a/klippy/kinematics/rotary_delta.py +++ b/klippy/kinematics/rotary_delta.py @@ -88,6 +88,11 @@ class RotaryDeltaKinematics: 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 @@ class RotaryDeltaKinematics: 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 11475d24c..c69d1b6dc 100644 --- a/klippy/kinematics/winch.py +++ b/klippy/kinematics/winch.py @@ -36,6 +36,9 @@ class WinchKinematics: 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 05e56cca4..fd44effb6 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -403,7 +403,7 @@ class PrinterRail: 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 04/28] 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 367e07659..a106abfd0 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 4e5108ca2..f11b38184 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 @@ done: 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 05/28] 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 7cb663a4a..145435427 100644 --- a/klippy/extras/heaters.py +++ b/klippy/extras/heaters.py @@ -14,6 +14,7 @@ KELVIN_TO_CELSIUS = -273.15 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 @@ class Heater: 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 @@ class Heater: 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 @@ class Heater: 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 @@ class Heater: 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 06/28] 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 f797d2b06..52ba3a490 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 8816ea2e3..214896e78 100644 --- a/klippy/extras/tmc2240.py +++ b/klippy/extras/tmc2240.py @@ -408,6 +408,8 @@ class TMC2240: 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) From 9a06d2b7e8b765ce7b141697aeb731e50201d2b8 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 21 Jan 2025 18:54:00 -0500 Subject: [PATCH 07/28] docs: Improve wording of main Klipper page Signed-off-by: Kevin O'Connor --- README.md | 15 +++++++-------- docs/index.md | 19 +++++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 32fe7331a..f8e8c9590 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,14 @@ Welcome to the Klipper project! https://www.klipper3d.org/ -Klipper is a 3d-Printer firmware. It combines the power of a general -purpose computer with one or more micro-controllers. See the +The Klipper firmware controls 3d-Printers. It combines the power of a +general purpose computer with one or more micro-controllers. See the [features document](https://www.klipper3d.org/Features.html) for more -information on why you should use Klipper. +information on why you should use the Klipper software. -To begin using Klipper start by -[installing](https://www.klipper3d.org/Installation.html) it. +Start by [installing Klipper software](https://www.klipper3d.org/Installation.html). -Klipper is Free Software. See the [license](COPYING) or read the -[documentation](https://www.klipper3d.org/Overview.html). We depend on -the generous support from our +Klipper software is Free Software. See the [license](COPYING) or read +the [documentation](https://www.klipper3d.org/Overview.html). We +depend on the generous support from our [sponsors](https://www.klipper3d.org/Sponsors.html). diff --git a/docs/index.md b/docs/index.md index 17192742b..4846f96d6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,13 +6,16 @@ title: Welcome ![](img/klipper-logo.png){ .center-image } -Klipper is a 3d-Printer firmware. It combines the power of a general -purpose computer with one or more micro-controllers. See the -[features](Features.md) document for more information on why you -should use Klipper. +The Klipper firmware controls 3d-Printers. It combines the power of a +general purpose computer with one or more micro-controllers. See the +[features document](https://www.klipper3d.org/Features.html) for more +information on why you should use the Klipper software. -To begin using Klipper start by [installing](Installation.md) it. +Start by [installing Klipper software](https://www.klipper3d.org/Installation.html). -Klipper is Free Software. Read the [documentation](Overview.md) or -view [the Klipper code on github](https://github.com/Klipper3d/klipper). -We depend on the generous support from our [sponsors](Sponsors.md). +Klipper software is Free Software. Read the +[documentation](https://www.klipper3d.org/Overview.html), see the +[license](COPYING), or +[download](https://github.com/Klipper3d/Klipper) the software. We +depend on the generous support from our +[sponsors](https://www.klipper3d.org/Sponsors.html). From 5fe333934d678a8dc3f8cdbddcf3ab828da5503e Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 21 Jan 2025 18:54:33 -0500 Subject: [PATCH 08/28] docs: Add a "Professional Services" link to Contacts page Signed-off-by: Kevin O'Connor --- docs/Contact.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/Contact.md b/docs/Contact.md index 714c36bd4..c2512be1e 100644 --- a/docs/Contact.md +++ b/docs/Contact.md @@ -132,3 +132,10 @@ There are several you have questions on the code then you can also ask in the [Klipper Discourse Forum](#discourse-forum) or on the [Klipper Discord Chat](#discord-chat). + +## Professional Services + +![](img/klipper-logo-small.png) + +Custom software development, software support, and solutions: +[https://ko-fi.com/koconnor](https://ko-fi.com/koconnor) From c72d73ec450119b5fbe13d98409037a21ae97101 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 10 Jan 2025 11:31:14 -0500 Subject: [PATCH 09/28] stepper_enable: Directly call clear_homing_state() on motor off event Call clear_homing_state() on each motor off event. This simplifies the kinematic classes as they no longer need to register and handle the motor_off event. Signed-off-by: Kevin O'Connor --- klippy/extras/stepper_enable.py | 1 + klippy/kinematics/cartesian.py | 4 ---- klippy/kinematics/corexy.py | 4 ---- klippy/kinematics/corexz.py | 4 ---- klippy/kinematics/delta.py | 4 ---- klippy/kinematics/deltesian.py | 4 ---- klippy/kinematics/hybrid_corexy.py | 4 ---- klippy/kinematics/hybrid_corexz.py | 4 ---- klippy/kinematics/polar.py | 4 ---- klippy/kinematics/rotary_delta.py | 4 ---- 10 files changed, 1 insertion(+), 36 deletions(-) diff --git a/klippy/extras/stepper_enable.py b/klippy/extras/stepper_enable.py index 621ff700e..4e8f3bbb6 100644 --- a/klippy/extras/stepper_enable.py +++ b/klippy/extras/stepper_enable.py @@ -94,6 +94,7 @@ class PrinterStepperEnable: print_time = toolhead.get_last_move_time() for el in self.enable_lines.values(): el.motor_disable(print_time) + toolhead.get_kinematics().clear_homing_state((0, 1, 2)) self.printer.send_event("stepper_enable:motor_off", print_time) toolhead.dwell(DISABLE_STALL_TIME) def motor_debug_enable(self, stepper, enable): diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index 2715d1720..1db40f4ff 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -40,8 +40,6 @@ class CartKinematics: for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) - self.printer.register_event_handler("stepper_enable:motor_off", - self._motor_off) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity, @@ -97,8 +95,6 @@ class CartKinematics: self.dc_module.home(homing_state) else: self.home_axis(homing_state, axis, self.rails[axis]) - def _motor_off(self, print_time): - 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 dc80d1eec..15169e50a 100644 --- a/klippy/kinematics/corexy.py +++ b/klippy/kinematics/corexy.py @@ -21,8 +21,6 @@ class CoreXYKinematics: for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) - config.get_printer().register_event_handler("stepper_enable:motor_off", - self._motor_off) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( @@ -63,8 +61,6 @@ class CoreXYKinematics: forcepos[axis] += 1.5 * (position_max - hi.position_endstop) # Perform homing homing_state.home_rails([rail], forcepos, homepos) - def _motor_off(self, print_time): - 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 b1c46146b..5298698d8 100644 --- a/klippy/kinematics/corexz.py +++ b/klippy/kinematics/corexz.py @@ -21,8 +21,6 @@ class CoreXZKinematics: for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) - config.get_printer().register_event_handler("stepper_enable:motor_off", - self._motor_off) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( @@ -63,8 +61,6 @@ class CoreXZKinematics: forcepos[axis] += 1.5 * (position_max - hi.position_endstop) # Perform homing homing_state.home_rails([rail], forcepos, homepos) - def _motor_off(self, print_time): - 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 04a75547b..f10586b7f 100644 --- a/klippy/kinematics/delta.py +++ b/klippy/kinematics/delta.py @@ -23,8 +23,6 @@ class DeltaKinematics: stepper_configs[2], need_position_minmax = False, default_position_endstop=a_endstop) self.rails = [rail_a, rail_b, rail_c] - config.get_printer().register_event_handler("stepper_enable:motor_off", - self._motor_off) # Setup max velocity self.max_velocity, self.max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( @@ -116,8 +114,6 @@ class DeltaKinematics: forcepos = list(self.home_position) 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.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 e48949af2..d08654750 100644 --- a/klippy/kinematics/deltesian.py +++ b/klippy/kinematics/deltesian.py @@ -41,8 +41,6 @@ class DeltesianKinematics: for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) - config.get_printer().register_event_handler( - "stepper_enable:motor_off", self._motor_off) self.limits = [(1.0, -1.0)] * 3 # X axis limits min_angle = config.getfloat('min_angle', MIN_ANGLE, @@ -146,8 +144,6 @@ class DeltesianKinematics: else: forcepos[1] += 1.5 * (position_max - hi.position_endstop) homing_state.home_rails([self.rails[2]], forcepos, homepos) - def _motor_off(self, print_time): - self.homed_axis = [False] * 3 def check_move(self, move): limits = list(map(list, self.limits)) spos, epos = move.start_pos, move.end_pos diff --git a/klippy/kinematics/hybrid_corexy.py b/klippy/kinematics/hybrid_corexy.py index 85f20bcc6..72b0b85b6 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -42,8 +42,6 @@ class HybridCoreXYKinematics: for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) - self.printer.register_event_handler("stepper_enable:motor_off", - self._motor_off) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( @@ -97,8 +95,6 @@ class HybridCoreXYKinematics: self.dc_module.home(homing_state) else: self.home_axis(homing_state, axis, self.rails[axis]) - def _motor_off(self, print_time): - 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 8e5c3d92e..041df8d77 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -42,8 +42,6 @@ class HybridCoreXZKinematics: for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) - self.printer.register_event_handler("stepper_enable:motor_off", - self._motor_off) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( @@ -97,8 +95,6 @@ class HybridCoreXZKinematics: self.dc_module.home(homing_state) else: self.home_axis(homing_state, axis, self.rails[axis]) - def _motor_off(self, print_time): - 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/polar.py b/klippy/kinematics/polar.py index fdd428a60..8369d386c 100644 --- a/klippy/kinematics/polar.py +++ b/klippy/kinematics/polar.py @@ -22,8 +22,6 @@ class PolarKinematics: for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) toolhead.register_step_generator(s.generate_steps) - config.get_printer().register_event_handler("stepper_enable:motor_off", - self._motor_off) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( @@ -88,8 +86,6 @@ class PolarKinematics: self._home_axis(homing_state, 0, self.rails[0]) if home_z: self._home_axis(homing_state, 2, self.rails[1]) - def _motor_off(self, print_time): - 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 46a63b822..89f963d22 100644 --- a/klippy/kinematics/rotary_delta.py +++ b/klippy/kinematics/rotary_delta.py @@ -21,8 +21,6 @@ class RotaryDeltaKinematics: stepper_configs[2], need_position_minmax=False, default_position_endstop=a_endstop, units_in_radians=True) self.rails = [rail_a, rail_b, rail_c] - config.get_printer().register_event_handler("stepper_enable:motor_off", - self._motor_off) # Read config max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity, @@ -101,8 +99,6 @@ class RotaryDeltaKinematics: #forcepos[2] = self.calibration.actuator_to_cartesian(min_angles)[2] forcepos[2] = -1. homing_state.home_rails(self.rails, forcepos, self.home_position) - def _motor_off(self, print_time): - 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 From 4aa550837fc170d0b77a0d461ca4f970b7bee7ae Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 10 Jan 2025 11:27:30 -0500 Subject: [PATCH 10/28] toolhead: Pass set_position() homing_axes parameter as a string Use strings such as "xyz" to specify which axes are to be considered homing during a set_position() call. This makes the parameter a little less cryptic. Signed-off-by: Kevin O'Connor --- klippy/extras/force_move.py | 2 +- klippy/extras/homing.py | 5 +++-- klippy/extras/homing_override.py | 4 ++-- klippy/extras/manual_stepper.py | 2 +- klippy/extras/safe_z_home.py | 2 +- klippy/kinematics/cartesian.py | 3 ++- klippy/kinematics/corexy.py | 2 +- klippy/kinematics/corexz.py | 2 +- klippy/kinematics/delta.py | 4 ++-- klippy/kinematics/deltesian.py | 7 ++++--- klippy/kinematics/hybrid_corexy.py | 3 ++- klippy/kinematics/hybrid_corexz.py | 3 ++- klippy/kinematics/polar.py | 4 ++-- klippy/kinematics/rotary_delta.py | 4 ++-- klippy/kinematics/winch.py | 2 +- klippy/toolhead.py | 2 +- 16 files changed, 28 insertions(+), 23 deletions(-) diff --git a/klippy/extras/force_move.py b/klippy/extras/force_move.py index 598783a48..0b2187fd7 100644 --- a/klippy/extras/force_move.py +++ b/klippy/extras/force_move.py @@ -136,7 +136,7 @@ class ForceMove: 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.set_position([x, y, z, curpos[3]], homing_axes="xyz") toolhead.get_kinematics().clear_homing_state(clear_axes) def load_config(config): diff --git a/klippy/extras/homing.py b/klippy/extras/homing.py index bbad53371..add209ecf 100644 --- a/klippy/extras/homing.py +++ b/klippy/extras/homing.py @@ -187,7 +187,8 @@ class Homing: # Notify of upcoming homing operation self.printer.send_event("homing:home_rails_begin", self, rails) # Alter kinematics class to think printer is at forcepos - homing_axes = [axis for axis in range(3) if forcepos[axis] is not None] + force_axes = [axis for axis in range(3) if forcepos[axis] is not None] + homing_axes = "".join(["xyz"[i] for i in force_axes]) startpos = self._fill_coord(forcepos) homepos = self._fill_coord(movepos) self.toolhead.set_position(startpos, homing_axes=homing_axes) @@ -231,7 +232,7 @@ class Homing: + self.adjust_pos.get(s.get_name(), 0.)) for s in kin.get_steppers()} newpos = kin.calc_position(kin_spos) - for axis in homing_axes: + for axis in force_axes: homepos[axis] = newpos[axis] self.toolhead.set_position(homepos) diff --git a/klippy/extras/homing_override.py b/klippy/extras/homing_override.py index e498c8b8d..0a987d59c 100644 --- a/klippy/extras/homing_override.py +++ b/klippy/extras/homing_override.py @@ -46,11 +46,11 @@ class HomingOverride: # Calculate forced position (if configured) toolhead = self.printer.lookup_object('toolhead') pos = toolhead.get_position() - homing_axes = [] + homing_axes = "" for axis, loc in enumerate(self.start_pos): if loc is not None: pos[axis] = loc - homing_axes.append(axis) + homing_axes += "xyz"[axis] toolhead.set_position(pos, homing_axes=homing_axes) # Perform homing context = self.template.create_template_context() diff --git a/klippy/extras/manual_stepper.py b/klippy/extras/manual_stepper.py index 40db4a503..9f21cc8d4 100644 --- a/klippy/extras/manual_stepper.py +++ b/klippy/extras/manual_stepper.py @@ -109,7 +109,7 @@ class ManualStepper: self.sync_print_time() def get_position(self): return [self.rail.get_commanded_position(), 0., 0., 0.] - def set_position(self, newpos, homing_axes=()): + def set_position(self, newpos, homing_axes=""): self.do_set_position(newpos[0]) def get_last_move_time(self): self.sync_print_time() diff --git a/klippy/extras/safe_z_home.py b/klippy/extras/safe_z_home.py index ca0756cfa..3ebaa13e7 100644 --- a/klippy/extras/safe_z_home.py +++ b/klippy/extras/safe_z_home.py @@ -37,7 +37,7 @@ class SafeZHoming: if 'z' not in kin_status['homed_axes']: # Always perform the z_hop if the Z axis is not homed pos[2] = 0 - toolhead.set_position(pos, homing_axes=[2]) + toolhead.set_position(pos, homing_axes="z") toolhead.manual_move([None, None, self.z_hop], self.z_hop_speed) toolhead.get_kinematics().clear_homing_state((2,)) diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index 1db40f4ff..0c22172b6 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -65,7 +65,8 @@ class CartKinematics: def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) - for axis in homing_axes: + for axis_name in homing_axes: + axis = "xyz".index(axis_name) if self.dc_module and axis == self.dc_module.axis: rail = self.dc_module.get_primary_rail().get_rail() else: diff --git a/klippy/kinematics/corexy.py b/klippy/kinematics/corexy.py index 15169e50a..891e58e0d 100644 --- a/klippy/kinematics/corexy.py +++ b/klippy/kinematics/corexy.py @@ -39,7 +39,7 @@ class CoreXYKinematics: def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) - if i in homing_axes: + if "xyz"[i] in homing_axes: self.limits[i] = rail.get_range() def clear_homing_state(self, axes): for i, _ in enumerate(self.limits): diff --git a/klippy/kinematics/corexz.py b/klippy/kinematics/corexz.py index 5298698d8..34def72a1 100644 --- a/klippy/kinematics/corexz.py +++ b/klippy/kinematics/corexz.py @@ -39,7 +39,7 @@ class CoreXZKinematics: def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) - if i in homing_axes: + if "xyz"[i] in homing_axes: self.limits[i] = rail.get_range() def clear_homing_state(self, axes): for i, _ in enumerate(self.limits): diff --git a/klippy/kinematics/delta.py b/klippy/kinematics/delta.py index f10586b7f..beb1826f0 100644 --- a/klippy/kinematics/delta.py +++ b/klippy/kinematics/delta.py @@ -88,7 +88,7 @@ class DeltaKinematics: math.sqrt(self.very_slow_xy2))) self.axes_min = toolhead.Coord(-max_xy, -max_xy, self.min_z, 0.) self.axes_max = toolhead.Coord(max_xy, max_xy, self.max_z, 0.) - self.set_position([0., 0., 0.], ()) + self.set_position([0., 0., 0.], "") def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] def _actuator_to_cartesian(self, spos): @@ -101,7 +101,7 @@ class DeltaKinematics: for rail in self.rails: rail.set_position(newpos) self.limit_xy2 = -1. - if tuple(homing_axes) == (0, 1, 2): + if homing_axes == "xyz": self.need_home = False def clear_homing_state(self, axes): # Clearing homing state for each axis individually is not implemented diff --git a/klippy/kinematics/deltesian.py b/klippy/kinematics/deltesian.py index d08654750..e0118b34a 100644 --- a/klippy/kinematics/deltesian.py +++ b/klippy/kinematics/deltesian.py @@ -87,7 +87,7 @@ class DeltesianKinematics: self.axes_min = toolhead.Coord(*[l[0] for l in self.limits], e=0.) self.axes_max = toolhead.Coord(*[l[1] for l in self.limits], e=0.) self.homed_axis = [False] * 3 - self.set_position([0., 0., 0.], ()) + self.set_position([0., 0., 0.], "") def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] def _actuator_to_cartesian(self, sp): @@ -113,8 +113,9 @@ class DeltesianKinematics: def set_position(self, newpos, homing_axes): for rail in self.rails: rail.set_position(newpos) - for n in homing_axes: - self.homed_axis[n] = True + for axis_name in homing_axes: + axis = "xyz".index(axis_name) + self.homed_axis[axis] = True def clear_homing_state(self, axes): for i, _ in enumerate(self.limits): if i in axes: diff --git a/klippy/kinematics/hybrid_corexy.py b/klippy/kinematics/hybrid_corexy.py index 72b0b85b6..6d72a89c8 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -67,7 +67,8 @@ class HybridCoreXYKinematics: def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) - for axis in homing_axes: + for axis_name in homing_axes: + axis = "xyz".index(axis_name) if self.dc_module and axis == self.dc_module.axis: rail = self.dc_module.get_primary_rail().get_rail() else: diff --git a/klippy/kinematics/hybrid_corexz.py b/klippy/kinematics/hybrid_corexz.py index 041df8d77..05fe096e5 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -67,7 +67,8 @@ class HybridCoreXZKinematics: def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) - for axis in homing_axes: + for axis_name in homing_axes: + axis = "xyz".index(axis_name) if self.dc_module and axis == self.dc_module.axis: rail = self.dc_module.get_primary_rail().get_rail() else: diff --git a/klippy/kinematics/polar.py b/klippy/kinematics/polar.py index 8369d386c..548b4bacd 100644 --- a/klippy/kinematics/polar.py +++ b/klippy/kinematics/polar.py @@ -45,9 +45,9 @@ class PolarKinematics: def set_position(self, newpos, homing_axes): for s in self.steppers: s.set_position(newpos) - if 2 in homing_axes: + if "z" in homing_axes: self.limit_z = self.rails[1].get_range() - if 0 in homing_axes and 1 in homing_axes: + if "x" in homing_axes and "y" in homing_axes: self.limit_xy2 = self.rails[0].get_range()[1]**2 def clear_homing_state(self, axes): if 0 in axes or 1 in axes: diff --git a/klippy/kinematics/rotary_delta.py b/klippy/kinematics/rotary_delta.py index 89f963d22..6b457ccb1 100644 --- a/klippy/kinematics/rotary_delta.py +++ b/klippy/kinematics/rotary_delta.py @@ -74,7 +74,7 @@ class RotaryDeltaKinematics: max_xy = math.sqrt(self.max_xy2) self.axes_min = toolhead.Coord(-max_xy, -max_xy, self.min_z, 0.) self.axes_max = toolhead.Coord(max_xy, max_xy, self.max_z, 0.) - self.set_position([0., 0., 0.], ()) + 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_position(self, stepper_positions): @@ -84,7 +84,7 @@ class RotaryDeltaKinematics: for rail in self.rails: rail.set_position(newpos) self.limit_xy2 = -1. - if tuple(homing_axes) == (0, 1, 2): + if homing_axes == "xyz": self.need_home = False def clear_homing_state(self, axes): # Clearing homing state for each axis individually is not implemented diff --git a/klippy/kinematics/winch.py b/klippy/kinematics/winch.py index c69d1b6dc..0003826b8 100644 --- a/klippy/kinematics/winch.py +++ b/klippy/kinematics/winch.py @@ -26,7 +26,7 @@ class WinchKinematics: acoords = list(zip(*self.anchors)) self.axes_min = toolhead.Coord(*[min(a) for a in acoords], e=0.) self.axes_max = toolhead.Coord(*[max(a) for a in acoords], e=0.) - self.set_position([0., 0., 0.], ()) + self.set_position([0., 0., 0.], "") def get_steppers(self): return list(self.steppers) def calc_position(self, stepper_positions): diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 256915daa..f1a9afda1 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -457,7 +457,7 @@ class ToolHead: # Movement commands def get_position(self): return list(self.commanded_pos) - def set_position(self, newpos, homing_axes=()): + def set_position(self, newpos, homing_axes=""): self.flush_step_generation() ffi_main, ffi_lib = chelper.get_ffi() ffi_lib.trapq_set_position(self.trapq, self.print_time, From 6ab253366cca1b5d2d8e9dec6ea243c0853612f7 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 10 Jan 2025 11:58:26 -0500 Subject: [PATCH 11/28] force_move: Use strings for axes to clear in clear_homing_state() Pass a string such as "xyz" to kin.clear_homing_state(). This makes the parameter a little less cryptic. Signed-off-by: Kevin O'Connor --- klippy/extras/force_move.py | 7 +++---- klippy/extras/safe_z_home.py | 2 +- klippy/extras/stepper_enable.py | 2 +- klippy/kinematics/cartesian.py | 8 ++++---- klippy/kinematics/corexy.py | 8 ++++---- klippy/kinematics/corexz.py | 8 ++++---- klippy/kinematics/delta.py | 4 ++-- klippy/kinematics/deltesian.py | 8 ++++---- klippy/kinematics/hybrid_corexy.py | 8 ++++---- klippy/kinematics/hybrid_corexz.py | 8 ++++---- klippy/kinematics/none.py | 2 +- klippy/kinematics/polar.py | 6 +++--- klippy/kinematics/rotary_delta.py | 4 ++-- klippy/kinematics/winch.py | 2 +- 14 files changed, 38 insertions(+), 39 deletions(-) diff --git a/klippy/extras/force_move.py b/klippy/extras/force_move.py index 0b2187fd7..24783c2c6 100644 --- a/klippy/extras/force_move.py +++ b/klippy/extras/force_move.py @@ -131,11 +131,10 @@ class ForceMove: x = gcmd.get_float('X', curpos[0]) y = gcmd.get_float('Y', curpos[1]) z = gcmd.get_float('Z', curpos[2]) - clear = gcmd.get('CLEAR', '').upper() - axes = ['X', 'Y', 'Z'] - clear_axes = [axes.index(a) for a in axes if a in clear] + clear = gcmd.get('CLEAR', '').lower() + clear_axes = "".join([a for a in "xyz" 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))) + x, y, z, clear_axes) toolhead.set_position([x, y, z, curpos[3]], homing_axes="xyz") toolhead.get_kinematics().clear_homing_state(clear_axes) diff --git a/klippy/extras/safe_z_home.py b/klippy/extras/safe_z_home.py index 3ebaa13e7..32b515b9a 100644 --- a/klippy/extras/safe_z_home.py +++ b/klippy/extras/safe_z_home.py @@ -40,7 +40,7 @@ class SafeZHoming: toolhead.set_position(pos, homing_axes="z") toolhead.manual_move([None, None, self.z_hop], self.z_hop_speed) - toolhead.get_kinematics().clear_homing_state((2,)) + toolhead.get_kinematics().clear_homing_state("z") 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/extras/stepper_enable.py b/klippy/extras/stepper_enable.py index 4e8f3bbb6..2bad75552 100644 --- a/klippy/extras/stepper_enable.py +++ b/klippy/extras/stepper_enable.py @@ -94,7 +94,7 @@ class PrinterStepperEnable: print_time = toolhead.get_last_move_time() for el in self.enable_lines.values(): el.motor_disable(print_time) - toolhead.get_kinematics().clear_homing_state((0, 1, 2)) + toolhead.get_kinematics().clear_homing_state("xyz") self.printer.send_event("stepper_enable:motor_off", print_time) toolhead.dwell(DISABLE_STALL_TIME) def motor_debug_enable(self, stepper, enable): diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index 0c22172b6..2a50a05de 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -72,10 +72,10 @@ class CartKinematics: else: rail = self.rails[axis] self.limits[axis] = rail.get_range() - def clear_homing_state(self, axes): - for i, _ in enumerate(self.limits): - if i in axes: - self.limits[i] = (1.0, -1.0) + def clear_homing_state(self, clear_axes): + for axis, axis_name in enumerate("xyz"): + if axis_name in clear_axes: + self.limits[axis] = (1.0, -1.0) def home_axis(self, homing_state, axis, rail): # Determine movement position_min, position_max = rail.get_range() diff --git a/klippy/kinematics/corexy.py b/klippy/kinematics/corexy.py index 891e58e0d..d68f8854f 100644 --- a/klippy/kinematics/corexy.py +++ b/klippy/kinematics/corexy.py @@ -41,10 +41,10 @@ class CoreXYKinematics: rail.set_position(newpos) if "xyz"[i] in homing_axes: self.limits[i] = rail.get_range() - def clear_homing_state(self, axes): - for i, _ in enumerate(self.limits): - if i in axes: - self.limits[i] = (1.0, -1.0) + def clear_homing_state(self, clear_axes): + for axis, axis_name in enumerate("xyz"): + if axis_name in clear_axes: + self.limits[axis] = (1.0, -1.0) def home(self, homing_state): # Each axis is homed independently and in order for axis in homing_state.get_axes(): diff --git a/klippy/kinematics/corexz.py b/klippy/kinematics/corexz.py index 34def72a1..8d3e027c3 100644 --- a/klippy/kinematics/corexz.py +++ b/klippy/kinematics/corexz.py @@ -41,10 +41,10 @@ class CoreXZKinematics: rail.set_position(newpos) if "xyz"[i] in homing_axes: self.limits[i] = rail.get_range() - def clear_homing_state(self, axes): - for i, _ in enumerate(self.limits): - if i in axes: - self.limits[i] = (1.0, -1.0) + def clear_homing_state(self, clear_axes): + for axis, axis_name in enumerate("xyz"): + if axis_name in clear_axes: + self.limits[axis] = (1.0, -1.0) def home(self, homing_state): # Each axis is homed independently and in order for axis in homing_state.get_axes(): diff --git a/klippy/kinematics/delta.py b/klippy/kinematics/delta.py index beb1826f0..aa244a8f3 100644 --- a/klippy/kinematics/delta.py +++ b/klippy/kinematics/delta.py @@ -103,9 +103,9 @@ class DeltaKinematics: self.limit_xy2 = -1. if homing_axes == "xyz": self.need_home = False - def clear_homing_state(self, axes): + def clear_homing_state(self, clear_axes): # Clearing homing state for each axis individually is not implemented - if 0 in axes or 1 in axes or 2 in axes: + if clear_axes: self.limit_xy2 = -1 self.need_home = True def home(self, homing_state): diff --git a/klippy/kinematics/deltesian.py b/klippy/kinematics/deltesian.py index e0118b34a..1f7ddaa05 100644 --- a/klippy/kinematics/deltesian.py +++ b/klippy/kinematics/deltesian.py @@ -116,10 +116,10 @@ class DeltesianKinematics: for axis_name in homing_axes: axis = "xyz".index(axis_name) self.homed_axis[axis] = True - def clear_homing_state(self, axes): - for i, _ in enumerate(self.limits): - if i in axes: - self.homed_axis[i] = False + def clear_homing_state(self, clear_axes): + for axis, axis_name in enumerate("xyz"): + if axis_name in clear_axes: + self.homed_axis[axis] = 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 6d72a89c8..fbaf49e4d 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -74,10 +74,10 @@ class HybridCoreXYKinematics: else: rail = self.rails[axis] self.limits[axis] = rail.get_range() - def clear_homing_state(self, axes): - for i, _ in enumerate(self.limits): - if i in axes: - self.limits[i] = (1.0, -1.0) + def clear_homing_state(self, clear_axes): + for axis, axis_name in enumerate("xyz"): + if axis_name in clear_axes: + self.limits[axis] = (1.0, -1.0) def home_axis(self, homing_state, axis, rail): position_min, position_max = rail.get_range() hi = rail.get_homing_info() diff --git a/klippy/kinematics/hybrid_corexz.py b/klippy/kinematics/hybrid_corexz.py index 05fe096e5..6fa120f77 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -74,10 +74,10 @@ class HybridCoreXZKinematics: else: rail = self.rails[axis] self.limits[axis] = rail.get_range() - def clear_homing_state(self, axes): - for i, _ in enumerate(self.limits): - if i in axes: - self.limits[i] = (1.0, -1.0) + def clear_homing_state(self, clear_axes): + for axis, axis_name in enumerate("xyz"): + if axis_name in clear_axes: + self.limits[axis] = (1.0, -1.0) def home_axis(self, homing_state, axis, rail): position_min, position_max = rail.get_range() hi = rail.get_homing_info() diff --git a/klippy/kinematics/none.py b/klippy/kinematics/none.py index fda1f073a..d9f9d21d2 100644 --- a/klippy/kinematics/none.py +++ b/klippy/kinematics/none.py @@ -13,7 +13,7 @@ class NoneKinematics: return [0, 0, 0] def set_position(self, newpos, homing_axes): pass - def clear_homing_state(self, axes): + def clear_homing_state(self, clear_axes): pass def home(self, homing_state): pass diff --git a/klippy/kinematics/polar.py b/klippy/kinematics/polar.py index 548b4bacd..73967c29c 100644 --- a/klippy/kinematics/polar.py +++ b/klippy/kinematics/polar.py @@ -49,11 +49,11 @@ class PolarKinematics: self.limit_z = self.rails[1].get_range() if "x" in homing_axes and "y" in homing_axes: self.limit_xy2 = self.rails[0].get_range()[1]**2 - def clear_homing_state(self, axes): - if 0 in axes or 1 in axes: + def clear_homing_state(self, clear_axes): + if "x" in clear_axes or "y" in clear_axes: # X and Y cannot be cleared separately self.limit_xy2 = -1. - if 2 in axes: + if "z" in clear_axes: self.limit_z = (1.0, -1.0) def _home_axis(self, homing_state, axis, rail): # Determine movement diff --git a/klippy/kinematics/rotary_delta.py b/klippy/kinematics/rotary_delta.py index 6b457ccb1..35f7ca986 100644 --- a/klippy/kinematics/rotary_delta.py +++ b/klippy/kinematics/rotary_delta.py @@ -86,9 +86,9 @@ class RotaryDeltaKinematics: self.limit_xy2 = -1. if homing_axes == "xyz": self.need_home = False - def clear_homing_state(self, axes): + def clear_homing_state(self, clear_axes): # Clearing homing state for each axis individually is not implemented - if 0 in axes or 1 in axes or 2 in axes: + if clear_axes: self.limit_xy2 = -1 self.need_home = True def home(self, homing_state): diff --git a/klippy/kinematics/winch.py b/klippy/kinematics/winch.py index 0003826b8..47bc68558 100644 --- a/klippy/kinematics/winch.py +++ b/klippy/kinematics/winch.py @@ -36,7 +36,7 @@ class WinchKinematics: def set_position(self, newpos, homing_axes): for s in self.steppers: s.set_position(newpos) - def clear_homing_state(self, axes): + def clear_homing_state(self, clear_axes): # XXX - homing not implemented pass def home(self, homing_state): From ed796fcfaa37b9308f12b7cdb0a942e0e4526474 Mon Sep 17 00:00:00 2001 From: Konstantin Koch Date: Wed, 22 Jan 2025 01:10:39 +0100 Subject: [PATCH 12/28] ads1x1x: added support for ADC chip (#6584) Added a temperature sensor configuration for ADS1103, ADS1104, ADS1105, ADS1113, ADS1114 and ADS1115 chips that can be used to add Analog to Digital Conversion capability to machines that don't have that on their own. Like Raspberry Pi's or if more analog input pins are needed than the chip provides like for RP2040. Generally they can be used for any analog input, but the typical use case is for temperature measurement. This code also has been written with temperature measurement in mind and not as a general ADC. Signed-off-by: Konstantin Koch Signed-off-by: Jack Wakefield --- docs/Config_Reference.md | 44 +++++ klippy/extras/ads1x1x.py | 393 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 437 insertions(+) create mode 100644 klippy/extras/ads1x1x.py diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 52ba3a490..0035955f5 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -4936,6 +4936,50 @@ vssa_pin: # noise. The default is 2 seconds. ``` +### [ads1x1x] + +ADS1013, ADS1014, ADS1015, ADS1113, ADS1114 and ADS1115 are I2C based Analog to +Digital Converters that can be used for temperature sensors. They provide 4 +analog input pins either as single line or as differential input. + +Note: Use caution if using this sensor to control heaters. The heater min_temp +and max_temp are only verified in the host and only if the host is running and +operating normally. (ADC inputs directly connected to the micro-controller +verify min_temp and max_temp within the micro-controller and do not require a +working connection to the host.) + +``` +[ads1x1x my_ads1x1x] +chip: ADS1115 +#pga: 4.096V +# Default value is 4.096V. The maximum voltage range used for the input. This +# scales all values read from the ADC. Options are: 6.144V, 4.096V, 2.048V, +# 1.024V, 0.512V, 0.256V +#adc_voltage: 3.3 +# The suppy voltage for the device. This allows additional software scaling +# for all values read from the ADC. +i2c_mcu: host +i2c_bus: i2c.1 +#address_pin: GND +# Default value is GND. There can be up to four addressed devices depending +# upon wiring of the device. Check the datasheet for details. The i2c_address +# can be specified directly instead of using the address_pin. +``` + +The chip provides pins that can be used on other sensors. + +``` +sensor_type: ... +# Can be any thermistor or adc_temperature. +sensor_pin: my_ads1x1x:AIN0 +# A combination of the name of the ads1x1x chip and the pin. Possible +# pin values are AIN0, AIN1, AIN2 and AIN3 for single ended lines and +# DIFF01, DIFF03, DIFF13 and DIFF23 for differential between their +# correspoding lines. For example +# DIFF03 measures the differential between line 0 and 3. Only specific +# combinations for the differentials are allowed. +``` + ### [replicape] Replicape support - see the [beaglebone guide](Beaglebone.md) and the diff --git a/klippy/extras/ads1x1x.py b/klippy/extras/ads1x1x.py new file mode 100644 index 000000000..1d72996f3 --- /dev/null +++ b/klippy/extras/ads1x1x.py @@ -0,0 +1,393 @@ +# Support for I2C based ADS1013, ADS1014, ADS1015, ADS1113, ADS1114 and ADS1115 +# +# Copyright (C) 2024 Konstantin Koch +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import logging +import pins +from . import bus + +# Supported chip types +ADS1X1X_CHIP_TYPE = { + 'ADS1013': 3, + 'ADS1014': 4, + 'ADS1015': 5, + 'ADS1113': 13, + 'ADS1114': 14, + 'ADS1115': 15 +} + +def isADS101X(chip): + return (chip == ADS1X1X_CHIP_TYPE['ADS1013'] \ + or chip == ADS1X1X_CHIP_TYPE['ADS1014'] \ + or chip == ADS1X1X_CHIP_TYPE['ADS1015']) + +def isADS111X(chip): + return (chip == ADS1X1X_CHIP_TYPE['ADS1113'] \ + or chip == ADS1X1X_CHIP_TYPE['ADS1114'] \ + or chip == ADS1X1X_CHIP_TYPE['ADS1115']) + +# Address is defined by how the address pin is wired +ADS1X1X_CHIP_ADDR = { + 'GND': 0x48, + 'VCC': 0x49, + 'SDA': 0x4a, + 'SCL': 0x4b +} + +# Chip "pointer" registers +ADS1X1X_REG_POINTER_MASK = 0x03 +ADS1X1X_REG_POINTER = { + 'CONVERSION': 0x00, + 'CONFIG': 0x01, + 'LO_THRESH': 0x02, + 'HI_THRESH': 0x03 +} + +# Config register masks +ADS1X1X_REG_CONFIG = { + 'OS_MASK': 0x8000, + 'MULTIPLEXER_MASK': 0x7000, + 'PGA_MASK': 0x0E00, + 'MODE_MASK': 0x0100, + 'DATA_RATE_MASK': 0x00E0, + 'COMPARATOR_MODE_MASK': 0x0010, + 'COMPARATOR_POLARITY_MASK': 0x0008, + # Determines if ALERT/RDY pin latches once asserted + 'COMPARATOR_LATCHING_MASK': 0x0004, + 'COMPARATOR_QUEUE_MASK': 0x0003 +} + +# +# The following enums are to be used with the configuration functions. +# +ADS1X1X_OS = { + 'OS_IDLE': 0x8000, # Device is not performing a conversion + 'OS_SINGLE': 0x8000 # Single-conversion +} + +ADS1X1X_MUX = { + 'DIFF01': 0x0000, # Differential P = AIN0, N = AIN1 0 + 'DIFF03': 0x1000, # Differential P = AIN0, N = AIN3 4096 + 'DIFF13': 0x2000, # Differential P = AIN1, N = AIN3 8192 + 'DIFF23': 0x3000, # Differential P = AIN2, N = AIN3 12288 + 'AIN0': 0x4000, # Single-ended (ADS1015: AIN0 16384) + 'AIN1': 0x5000, # Single-ended (ADS1015: AIN1 20480) + 'AIN2': 0x6000, # Single-ended (ADS1015: AIN2 24576) + 'AIN3': 0x7000 # Single-ended (ADS1015: AIN3 28672) +} + +ADS1X1X_PGA = { + '6.144V': 0x0000, # +/-6.144V range = Gain 2/3 + '4.096V': 0x0200, # +/-4.096V range = Gain 1 + '2.048V': 0x0400, # +/-2.048V range = Gain 2 + '1.024V': 0x0600, # +/-1.024V range = Gain 4 + '0.512V': 0x0800, # +/-0.512V range = Gain 8 + '0.256V': 0x0A00 # +/-0.256V range = Gain 16 +} +ADS1X1X_PGA_VALUE = { + 0x0000: 6.144, + 0x0200: 4.096, + 0x0400: 2.048, + 0x0600: 1.024, + 0x0800: 0.512, + 0x0A00: 0.256, +} +ADS111X_RESOLUTION = 32767.0 +ADS111X_PGA_SCALAR = { + 0x0000: 6.144 / ADS111X_RESOLUTION, # +/-6.144V range = Gain 2/3 + 0x0200: 4.096 / ADS111X_RESOLUTION, # +/-4.096V range = Gain 1 + 0x0400: 2.048 / ADS111X_RESOLUTION, # +/-2.048V range = Gain 2 + 0x0600: 1.024 / ADS111X_RESOLUTION, # +/-1.024V range = Gain 4 + 0x0800: 0.512 / ADS111X_RESOLUTION, # +/-0.512V range = Gain 8 + 0x0A00: 0.256 / ADS111X_RESOLUTION # +/-0.256V range = Gain 16 +} +ADS101X_RESOLUTION = 2047.0 +ADS101X_PGA_SCALAR = { + 0x0000: 6.144 / ADS101X_RESOLUTION, # +/-6.144V range = Gain 2/3 + 0x0200: 4.096 / ADS101X_RESOLUTION, # +/-4.096V range = Gain 1 + 0x0400: 2.048 / ADS101X_RESOLUTION, # +/-2.048V range = Gain 2 + 0x0600: 1.024 / ADS101X_RESOLUTION, # +/-1.024V range = Gain 4 + 0x0800: 0.512 / ADS101X_RESOLUTION, # +/-0.512V range = Gain 8 + 0x0A00: 0.256 / ADS101X_RESOLUTION # +/-0.256V range = Gain 16 +} +ADS1X1X_MODE = { + 'continuous': 0x0000, # Continuous conversion mode + 'single': 0x0100 # Power-down single-shot mode +} + +# Lesser samples per second means it takes and averages more samples before +# returning a result. +ADS101X_SAMPLES_PER_SECOND = { + '128': 0x0000, # 128 samples per second + '250': 0x0020, # 250 samples per second + '490': 0x0040, # 490 samples per second + '920': 0x0060, # 920 samples per second + '1600': 0x0080, # 1600 samples per second + '2400': 0x00a0, # 2400 samples per second + '3300': 0x00c0, # 3300 samples per second +} + +ADS111X_SAMPLES_PER_SECOND = { + '8': 0x0000, # 8 samples per second + '16': 0x0020, # 16 samples per second + '32': 0x0040, # 32 samples per second + '64': 0x0060, # 64 samples per second + '128': 0x0080, # 128 samples per second + '250': 0x00a0, # 250 samples per second + '475': 0x00c0, # 475 samples per second + '860': 0x00e0 # 860 samples per second +} + +ADS1X1X_COMPARATOR_MODE = { + 'TRADITIONAL': 0x0000, # Traditional comparator with hysteresis + 'WINDOW': 0x0010 # Window comparator +} + +ADS1X1X_COMPARATOR_POLARITY = { + 'ACTIVE_LO': 0x0000, # ALERT/RDY pin is low when active + 'ACTIVE_HI': 0x0008 # ALERT/RDY pin is high when active +} + +ADS1X1X_COMPARATOR_LATCHING = { + 'NON_LATCHING': 0x0000, # Non-latching comparator + 'LATCHING': 0x0004 # Latching comparator +} + +ADS1X1X_COMPARATOR_QUEUE = { + 'QUEUE_1': 0x0000, # Assert ALERT/RDY after one conversions + 'QUEUE_2': 0x0001, # Assert ALERT/RDY after two conversions + 'QUEUE_4': 0x0002, # Assert ALERT/RDY after four conversions + 'QUEUE_NONE': 0x0003 # Disable the comparator and put ALERT/RDY + # in high state +} + +ADS1X1_OPERATIONS = { + 'SET_MUX': 0, + 'READ_CONVERSION': 1 +} + +class ADS1X1X_chip: + + def __init__(self, config): + self._printer = config.get_printer() + self._reactor = self._printer.get_reactor() + + self.name = config.get_name().split()[-1] + self.chip = config.getchoice('chip', ADS1X1X_CHIP_TYPE) + address = ADS1X1X_CHIP_ADDR['GND'] + # If none is specified, i2c_address can be used for a specific address + if config.get('address_pin', None) is not None: + address = config.getchoice('address_pin', ADS1X1X_CHIP_ADDR) + + self._ppins = self._printer.lookup_object("pins") + self._ppins.register_chip(self.name, self) + + self.pga = config.getchoice('pga', ADS1X1X_PGA, '4.096V') + self.adc_voltage = config.getfloat('adc_voltage', above=0., default=3.3) + # Comparators are not implemented, they would only be useful if the + # alert pin is used, which we haven't made configurable. + # But that wouldn't be useful for a normal temperature sensor anyway. + self.comp_mode = ADS1X1X_COMPARATOR_MODE['TRADITIONAL'] + self.comp_polarity = ADS1X1X_COMPARATOR_POLARITY['ACTIVE_LO'] + self.comp_latching = ADS1X1X_COMPARATOR_LATCHING['NON_LATCHING'] + self.comp_queue = ADS1X1X_COMPARATOR_QUEUE['QUEUE_NONE'] + self._i2c = bus.MCU_I2C_from_config(config, address) + + self.mcu = self._i2c.get_mcu() + + self._printer.add_object("ads1x1x " + self.name, self) + self._printer.register_event_handler("klippy:connect", \ + self._handle_connect) + + self._pins = {} + self._mutex = self._reactor.mutex() + + def setup_pin(self, pin_type, pin_params): + pin = pin_params['pin'] + if pin_type == 'adc': + if (pin not in ADS1X1X_MUX): + raise pins.error('ADS1x1x pin %s is not valid' % \ + pin_params['pin']) + + config = 0 + config |= (ADS1X1X_OS['OS_SINGLE'] & \ + ADS1X1X_REG_CONFIG['OS_MASK']) + config |= (ADS1X1X_MUX[pin_params['pin']] & \ + ADS1X1X_REG_CONFIG['MULTIPLEXER_MASK']) + config |= (self.pga & ADS1X1X_REG_CONFIG['PGA_MASK']) + # Have to use single mode, because in continuous, it never reaches + # idle state, which we use to determine if the sampling is done. + config |= (ADS1X1X_MODE['single'] & \ + ADS1X1X_REG_CONFIG['MODE_MASK']) + # lowest sample rate per default, until report time has been set in + # setup_adc_sample + config |= (self.comp_mode \ + & ADS1X1X_REG_CONFIG['COMPARATOR_MODE_MASK']) + config |= (self.comp_polarity \ + & ADS1X1X_REG_CONFIG['COMPARATOR_POLARITY_MASK']) + config |= (self.comp_latching \ + & ADS1X1X_REG_CONFIG['COMPARATOR_LATCHING_MASK']) + config |= (self.comp_queue \ + & ADS1X1X_REG_CONFIG['COMPARATOR_QUEUE_MASK']) + + pin_obj = ADS1X1X_pin(self, config) + if pin in self._pins: + raise pins.error( + 'pin %s for chip %s is used multiple times' \ + % (pin, self.name)) + self._pins[pin] = pin_obj + + return pin_obj + raise pins.error('Wrong pin or incompatible type: %s with type %s! ' % ( + pin, pin_type)) + + def _handle_connect(self): + try: + # Init all devices on bus for this kind of device + self._i2c.i2c_write([0x06, 0x00, 0x00]) + except Exception: + logging.exception("ADS1X1X: error while resetting device") + + def is_ready(self): + config = self._read_register(ADS1X1X_REG_POINTER['CONFIG']) + return bool((config & ADS1X1X_REG_CONFIG['OS_MASK']) == \ + ADS1X1X_OS['OS_IDLE']) + + def calculate_sample_rate(self): + pin_count = len(self._pins) + lowest_report_time = 1 + for pin in self._pins.values(): + lowest_report_time = min(lowest_report_time, pin.report_time) + + sample_rate = 1 / lowest_report_time * pin_count + samples_per_second = ADS111X_SAMPLES_PER_SECOND + if isADS101X(self.chip): + samples_per_second = ADS101X_SAMPLES_PER_SECOND + + # make sure the samples list is sorted correctly by number. + samples_per_second = sorted(samples_per_second.items(), \ + key=lambda t: int(t[0])) + for rate, bits in samples_per_second: + rate_number = int(rate) + if sample_rate <= rate_number: + return (rate_number, bits) + logging.warning( + "ADS1X1X: requested sample rate %s is higher than supported by %s."\ + % (sample_rate, self.name)) + return (rate_number, bits) + + def handle_report_time_update(self): + (sample_rate, sample_rate_bits) = self.calculate_sample_rate() + + for pin in self._pins.values(): + pin.config = (pin.config & ~ADS1X1X_REG_CONFIG['DATA_RATE_MASK']) \ + | (sample_rate_bits & ADS1X1X_REG_CONFIG['DATA_RATE_MASK']) + + self.delay = 1 / float(sample_rate) + + def sample(self, pin): + with self._mutex: + try: + self._write_register(ADS1X1X_REG_POINTER['CONFIG'], pin.config) + self._reactor.pause(self._reactor.monotonic() + self.delay) + start_time = self._reactor.monotonic() + while not self.is_ready(): + self._reactor.pause(self._reactor.monotonic() + 0.001) + # if we waited twice the expected time, mark this an error + if start_time + self.delay < self._reactor.monotonic(): + logging.warning("ADS1X1X: timeout during sampling") + return None + return self._read_register(ADS1X1X_REG_POINTER['CONVERSION']) + except Exception as e: + logging.exception("ADS1X1X: error while sampling: %s" % str(e)) + return None + + def _read_register(self, reg): + # read a single register + params = self._i2c.i2c_read([reg], 2) + buff = bytearray(params['response']) + return (buff[0]<<8 | buff[1]) + + def _write_register(self, reg, data): + data = [ + (reg & 0xFF), # Control register + ((data>>8) & 0xFF), # High byte + (data & 0xFF), # Lo byte + ] + self._i2c.i2c_write(data) + +class ADS1X1X_pin: + def __init__(self, chip, config): + self.mcu = chip.mcu + self.chip = chip + self.config = config + + self.invalid_count = 0 + + self.chip._printer.register_event_handler("klippy:connect", \ + self._handle_connect) + + def _handle_connect(self): + self._reactor = self.chip._printer.get_reactor() + self._sample_timer = \ + self._reactor.register_timer(self._process_sample, \ + self._reactor.NOW) + + def _process_sample(self, eventtime): + sample = self.chip.sample(self) + if sample is not None: + # The sample is encoded in the top 12 or full 16 bits + # Value's meaning is defined by ADS1X1X_REG_CONFIG['PGA_MASK'] + if isADS101X(self.chip.chip): + sample >>= 4 + target_value = sample / ADS101X_RESOLUTION + else: + target_value = sample / ADS111X_RESOLUTION + + # Thermistors expect a value between 0 and 1 to work. If we use a + # PGA with 4.096V but supply only 3.3V, the reference voltage for + # voltage divider is only 3.3V, not 4.096V. So we remap the range + # from what the PGA allows as range to end up between 0 and 1 for + # the thermistor logic to work as expected. + target_value = target_value * (ADS1X1X_PGA_VALUE[self.chip.pga] / \ + self.chip.adc_voltage) + + if target_value > self.maxval or target_value < self.minval: + self.invalid_count = self.invalid_count + 1 + logging.warning("ADS1X1X: temperature outside range") + self.check_invalid() + else: + self.invalid_count = 0 + + # Publish result + measured_time = self._reactor.monotonic() + self.callback(self.chip.mcu.estimated_print_time(measured_time), + target_value) + else: + self.invalid_count = self.invalid_count + 1 + self.check_invalid() + + return eventtime + self.report_time + + def check_invalid(self): + if self.invalid_count > self.range_check_count: + self.chip._printer.invoke_shutdown( + "ADS1X1X temperature check failed") + + def get_mcu(self): + return self.mcu + + def setup_adc_callback(self, report_time, callback): + self.report_time = report_time + self.callback = callback + self.chip.handle_report_time_update() + + def setup_adc_sample(self, sample_time, sample_count, + minval=0., maxval=1., range_check_count=0): + self.minval = minval + self.maxval = maxval + self.range_check_count = range_check_count + +def load_config_prefix(config): + return ADS1X1X_chip(config) From 8c1037ef1bb0afa7470974b454df0307fef36ebc Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Fri, 24 Jan 2025 11:04:45 +0000 Subject: [PATCH 13/28] screws_tilt_adjust: initialize status result as a dictionary Signed-off-by: Pedro Lamas --- klippy/extras/screws_tilt_adjust.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/klippy/extras/screws_tilt_adjust.py b/klippy/extras/screws_tilt_adjust.py index 521744af3..fec54185e 100644 --- a/klippy/extras/screws_tilt_adjust.py +++ b/klippy/extras/screws_tilt_adjust.py @@ -12,7 +12,7 @@ class ScrewsTiltAdjust: self.config = config self.printer = config.get_printer() self.screws = [] - self.results = [] + self.results = {} self.max_diff = None self.max_diff_error = False # Read config From 0114d72a6cd8140d7dc80a590fc50e7073069edc Mon Sep 17 00:00:00 2001 From: John Date: Fri, 24 Jan 2025 21:46:08 -0500 Subject: [PATCH 14/28] config: Update generic-creality-v4.2.7.cfg (#6790) Corrected Filament Runout Sensor Pin (as per schematic - see https://github.com/LeeOtts/Ender3v2-Klipper-Configs/blob/main/Creality.4.2.7.-.Schematic.28-5-22-1.pdf ) Signed-off-by: John Minor --- config/generic-creality-v4.2.7.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/generic-creality-v4.2.7.cfg b/config/generic-creality-v4.2.7.cfg index 21e99da59..6b8644c5e 100644 --- a/config/generic-creality-v4.2.7.cfg +++ b/config/generic-creality-v4.2.7.cfg @@ -95,4 +95,4 @@ max_z_accel: 100 aliases: EXP1_1=PC6,EXP1_3=PB10,EXP1_5=PB14,EXP1_7=PB12,EXP1_9=, EXP1_2=PB2,EXP1_4=PB11,EXP1_6=PB13,EXP1_8=PB15,EXP1_10=<5V>, - PROBE_IN=PB0,PROBE_OUT=PB1,FIL_RUNOUT=PC6 + PROBE_IN=PB0,PROBE_OUT=PB1,FIL_RUNOUT=PA4 From d57fe4395e77b0b6f5eabccf890498049fabbcfe Mon Sep 17 00:00:00 2001 From: Branden Cash <203336+ammmze@users.noreply.github.com> Date: Sun, 2 Feb 2025 16:40:43 -0700 Subject: [PATCH 15/28] garbage_collection: freeze objects on klippy ready (#6794) This significantly reduces the amount of data in the generation 2 garbage collection bucket from the initial startup of klipper. Signed-off-by: Branden Cash <203336+ammmze@users.noreply.github.com> --- klippy/extras/garbage_collection.py | 31 +++++++++++++++++++++++++++++ klippy/toolhead.py | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 klippy/extras/garbage_collection.py diff --git a/klippy/extras/garbage_collection.py b/klippy/extras/garbage_collection.py new file mode 100644 index 000000000..da51df3b8 --- /dev/null +++ b/klippy/extras/garbage_collection.py @@ -0,0 +1,31 @@ +# Garbage collection optimizations +# +# Copyright (C) 2025 Branden Cash +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import gc +import logging + +class GarbageCollection: + def __init__(self, config): + self.printer = config.get_printer() + # feature check ... freeze/unfreeze is only available in python 3.7+ + can_freeze = hasattr(gc, 'freeze') and hasattr(gc, 'unfreeze') + if can_freeze: + self.printer.register_event_handler("klippy:ready", + self._handle_ready) + self.printer.register_event_handler("klippy:disconnect", + self._handle_disconnect) + + def _handle_ready(self): + logging.debug("Running full garbage collection and freezing") + for n in range(3): + gc.collect(n) + gc.freeze() + + def _handle_disconnect(self): + logging.debug("Unfreezing garbage collection") + gc.unfreeze() + +def load_config(config): + return GarbageCollection(config) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index f1a9afda1..e15f987e5 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -290,7 +290,7 @@ class ToolHead: self._handle_shutdown) # Load some default modules modules = ["gcode_move", "homing", "idle_timeout", "statistics", - "manual_probe", "tuning_tower"] + "manual_probe", "tuning_tower", "garbage_collection"] for module_name in modules: self.printer.load_object(config, module_name) # Print time and flush tracking From 6cdcf75e6bc931ec1e6c1b1b2fd148f9e66d9df0 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 14 Jan 2025 15:26:05 -0500 Subject: [PATCH 16/28] stm32: Add support for reporting canbus state from can.c Signed-off-by: Kevin O'Connor --- src/generic/canbus.h | 11 ++++++++++ src/stm32/can.c | 48 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/generic/canbus.h b/src/generic/canbus.h index f21a7cc45..a0a2c2abe 100644 --- a/src/generic/canbus.h +++ b/src/generic/canbus.h @@ -17,9 +17,20 @@ struct canbus_msg { #define CANMSG_DATA_LEN(msg) ((msg)->dlc > 8 ? 8 : (msg)->dlc) +struct canbus_status { + uint32_t rx_error, tx_error, tx_retries; + uint32_t bus_state; +}; + +enum { + CANBUS_STATE_ACTIVE, CANBUS_STATE_WARN, CANBUS_STATE_PASSIVE, + CANBUS_STATE_OFF, +}; + // callbacks provided by board specific code int canhw_send(struct canbus_msg *msg); void canhw_set_filter(uint32_t id); +void canhw_get_status(struct canbus_status *status); // canbus.c int canbus_send(struct canbus_msg *msg); diff --git a/src/stm32/can.c b/src/stm32/can.c index dc9153282..6d35e677b 100644 --- a/src/stm32/can.c +++ b/src/stm32/can.c @@ -2,7 +2,7 @@ // // Copyright (C) 2019 Eug Krashtan // Copyright (C) 2020 Pontus Borg -// Copyright (C) 2021 Kevin O'Connor +// Copyright (C) 2021-2025 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -168,6 +168,31 @@ canhw_set_filter(uint32_t id) fcan->FMR = fmr & ~CAN_FMR_FINIT; } +static struct { + uint32_t rx_error, tx_error; +} CAN_Errors; + +// Report interface status +void +canhw_get_status(struct canbus_status *status) +{ + irqstatus_t flag = irq_save(); + uint32_t esr = SOC_CAN->ESR; + uint32_t rx_error = CAN_Errors.rx_error, tx_error = CAN_Errors.tx_error; + irq_restore(flag); + + status->rx_error = rx_error; + status->tx_error = tx_error; + if (esr & CAN_ESR_BOFF) + status->bus_state = CANBUS_STATE_OFF; + else if (esr & CAN_ESR_EPVF) + status->bus_state = CANBUS_STATE_PASSIVE; + else if (esr & CAN_ESR_EWGF) + status->bus_state = CANBUS_STATE_WARN; + else + status->bus_state = 0; +} + // This function handles CAN global interrupts void CAN_IRQHandler(void) @@ -190,6 +215,8 @@ CAN_IRQHandler(void) // Process packet canbus_process_data(&msg); } + + // Check for transmit ready uint32_t ier = SOC_CAN->IER; if (ier & CAN_IER_TMEIE && SOC_CAN->TSR & (CAN_TSR_RQCP0|CAN_TSR_RQCP1|CAN_TSR_RQCP2)) { @@ -197,6 +224,21 @@ CAN_IRQHandler(void) SOC_CAN->IER = ier & ~CAN_IER_TMEIE; canbus_notify_tx(); } + + // Check for error irq + uint32_t msr = SOC_CAN->MSR; + if (msr & CAN_MSR_ERRI) { + uint32_t esr = SOC_CAN->ESR; + uint32_t lec = (esr & CAN_ESR_LEC_Msk) >> CAN_ESR_LEC_Pos; + if (lec && lec != 7) { + SOC_CAN->ESR = 7 << CAN_ESR_LEC_Pos; + if (lec >= 3 && lec <= 5) + CAN_Errors.tx_error += 1; + else + CAN_Errors.rx_error += 1; + } + SOC_CAN->MSR = CAN_MSR_ERRI; + } } static inline const uint32_t @@ -289,6 +331,8 @@ can_init(void) armcm_enable_irq(CAN_IRQHandler, CAN_RX1_IRQn, 0); if (CAN_RX0_IRQn != CAN_TX_IRQn) armcm_enable_irq(CAN_IRQHandler, CAN_TX_IRQn, 0); - SOC_CAN->IER = CAN_IER_FMPIE0; + if (CAN_RX0_IRQn != CAN_SCE_IRQn) + armcm_enable_irq(CAN_IRQHandler, CAN_SCE_IRQn, 0); + SOC_CAN->IER = CAN_IER_FMPIE0 | CAN_IER_ERRIE; } DECL_INIT(can_init); From b7366ae3fc57d3d50542ac297c41db76217a9094 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 14 Jan 2025 15:46:03 -0500 Subject: [PATCH 17/28] stm32: Add support for reporting canbus state from fdcan.c Signed-off-by: Kevin O'Connor --- src/stm32/fdcan.c | 49 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/stm32/fdcan.c b/src/stm32/fdcan.c index 5344d26b1..8a2d1ab2a 100644 --- a/src/stm32/fdcan.c +++ b/src/stm32/fdcan.c @@ -1,11 +1,12 @@ // FDCAN support on stm32 chips // -// Copyright (C) 2021-2022 Kevin O'Connor +// Copyright (C) 2021-2025 Kevin O'Connor // Copyright (C) 2019 Eug Krashtan // Copyright (C) 2020 Pontus Borg // // This file may be distributed under the terms of the GNU GPLv3 license. +#include "board/irq.h" // irq_save #include "command.h" // DECL_CONSTANT_STR #include "generic/armcm_boot.h" // armcm_enable_irq #include "generic/canbus.h" // canbus_notify_tx @@ -184,6 +185,38 @@ canhw_set_filter(uint32_t id) SOC_CAN->CCCR &= ~FDCAN_CCCR_INIT; } +static struct { + uint32_t rx_error, tx_error; +} CAN_Errors; + +// Report interface status +void +canhw_get_status(struct canbus_status *status) +{ + irqstatus_t flag = irq_save(); + uint32_t psr = SOC_CAN->PSR, lec = psr & FDCAN_PSR_LEC_Msk; + if (lec && lec != 7) { + // Reading PSR clears it - so update state here + if (lec >= 3 && lec <= 5) + CAN_Errors.tx_error += 1; + else + CAN_Errors.rx_error += 1; + } + uint32_t rx_error = CAN_Errors.rx_error, tx_error = CAN_Errors.tx_error; + irq_restore(flag); + + status->rx_error = rx_error; + status->tx_error = tx_error; + if (psr & FDCAN_PSR_BO) + status->bus_state = CANBUS_STATE_OFF; + else if (psr & FDCAN_PSR_EP) + status->bus_state = CANBUS_STATE_PASSIVE; + else if (psr & FDCAN_PSR_EW) + status->bus_state = CANBUS_STATE_WARN; + else + status->bus_state = 0; +} + // This function handles CAN global interrupts void CAN_IRQHandler(void) @@ -220,6 +253,18 @@ CAN_IRQHandler(void) SOC_CAN->IR = FDCAN_IE_TC; canbus_notify_tx(); } + if (ir & (FDCAN_IR_PED | FDCAN_IR_PEA)) { + // Bus error + uint32_t psr = SOC_CAN->PSR; + SOC_CAN->IR = FDCAN_IR_PED | FDCAN_IR_PEA; + uint32_t lec = psr & FDCAN_PSR_LEC_Msk; + if (lec && lec != 7) { + if (lec >= 3 && lec <= 5) + CAN_Errors.tx_error += 1; + else + CAN_Errors.rx_error += 1; + } + } } static inline const uint32_t @@ -320,6 +365,6 @@ can_init(void) /*##-3- Configure Interrupts #################################*/ armcm_enable_irq(CAN_IRQHandler, CAN_IT0_IRQn, 1); SOC_CAN->ILE = FDCAN_ILE_EINT0; - SOC_CAN->IE = FDCAN_IE_RF0NE | FDCAN_IE_TC; + SOC_CAN->IE = FDCAN_IE_RF0NE | FDCAN_IE_TC | FDCAN_IE_PEDE | FDCAN_IE_PEAE; } DECL_INIT(can_init); From 9fd415d3f55e4be7e0b0b49a0b65cd8c174e01da Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 14 Jan 2025 15:57:01 -0500 Subject: [PATCH 18/28] rp2040: Add support for reporting canbus state Signed-off-by: Kevin O'Connor --- src/rp2040/can.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/rp2040/can.c b/src/rp2040/can.c index 1f4c281ec..0e62ac3b2 100644 --- a/src/rp2040/can.c +++ b/src/rp2040/can.c @@ -1,6 +1,6 @@ // Serial over CAN emulation for rp2040 using can2040 software canbus // -// Copyright (C) 2022 Kevin O'Connor +// Copyright (C) 2022-2025 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -41,6 +41,23 @@ canhw_set_filter(uint32_t id) // Filter not implemented (and not necessary) } +static uint32_t last_tx_retries; + +// Report interface status +void +canhw_get_status(struct canbus_status *status) +{ + struct can2040_stats stats; + can2040_get_statistics(&cbus, &stats); + uint32_t tx_extra = stats.tx_attempt - stats.tx_total; + if (last_tx_retries != tx_extra) + last_tx_retries = tx_extra - 1; + + status->rx_error = stats.parse_error; + status->tx_retries = last_tx_retries; + status->bus_state = CANBUS_STATE_ACTIVE; +} + // can2040 callback function - handle rx and tx notifications static void can2040_cb(struct can2040 *cd, uint32_t notify, struct can2040_msg *msg) From 61fb5fe29c0c38049c173aa13fdbcffb386aa1c8 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 14 Jan 2025 16:06:45 -0500 Subject: [PATCH 19/28] atsamd: Add support for reporting canbus state Signed-off-by: Kevin O'Connor --- src/atsamd/fdcan.c | 49 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/atsamd/fdcan.c b/src/atsamd/fdcan.c index 5ad62c0f7..e8c107654 100644 --- a/src/atsamd/fdcan.c +++ b/src/atsamd/fdcan.c @@ -1,11 +1,12 @@ // CANbus support on atsame51 chips // -// Copyright (C) 2021-2022 Kevin O'Connor +// Copyright (C) 2021-2025 Kevin O'Connor // Copyright (C) 2019 Eug Krashtan // Copyright (C) 2020 Pontus Borg // // This file may be distributed under the terms of the GNU GPLv3 license. +#include "board/irq.h" // irq_save #include "command.h" // DECL_CONSTANT_STR #include "generic/armcm_boot.h" // armcm_enable_irq #include "generic/canbus.h" // canbus_notify_tx @@ -163,6 +164,38 @@ canhw_set_filter(uint32_t id) CANx->CCCR.reg &= ~CAN_CCCR_INIT; } +static struct { + uint32_t rx_error, tx_error; +} CAN_Errors; + +// Report interface status +void +canhw_get_status(struct canbus_status *status) +{ + irqstatus_t flag = irq_save(); + uint32_t psr = CANx->PSR.reg, lec = psr & CAN_PSR_LEC_Msk; + if (lec && lec != 7) { + // Reading PSR clears it - so update state here + if (lec >= 3 && lec <= 5) + CAN_Errors.tx_error += 1; + else + CAN_Errors.rx_error += 1; + } + uint32_t rx_error = CAN_Errors.rx_error, tx_error = CAN_Errors.tx_error; + irq_restore(flag); + + status->rx_error = rx_error; + status->tx_error = tx_error; + if (psr & CAN_PSR_BO) + status->bus_state = CANBUS_STATE_OFF; + else if (psr & CAN_PSR_EP) + status->bus_state = CANBUS_STATE_PASSIVE; + else if (psr & CAN_PSR_EW) + status->bus_state = CANBUS_STATE_WARN; + else + status->bus_state = 0; +} + // This function handles CAN global interrupts void CAN_IRQHandler(void) @@ -199,6 +232,18 @@ CAN_IRQHandler(void) CANx->IR.reg = FDCAN_IE_TC; canbus_notify_tx(); } + if (ir & (CAN_IR_PED | CAN_IR_PEA)) { + // Bus error + uint32_t psr = CANx->PSR.reg; + CANx->IR.reg = CAN_IR_PED | CAN_IR_PEA; + uint32_t lec = psr & CAN_PSR_LEC_Msk; + if (lec && lec != 7) { + if (lec >= 3 && lec <= 5) + CAN_Errors.tx_error += 1; + else + CAN_Errors.rx_error += 1; + } + } } static inline const uint32_t @@ -309,6 +354,6 @@ can_init(void) /*##-3- Configure Interrupts #################################*/ armcm_enable_irq(CAN_IRQHandler, CANx_IRQn, 1); CANx->ILE.reg = CAN_ILE_EINT0; - CANx->IE.reg = CAN_IE_RF0NE | FDCAN_IE_TC; + CANx->IE.reg = CAN_IE_RF0NE | FDCAN_IE_TC | CAN_IE_PEDE | CAN_IE_PEAE; } DECL_INIT(can_init); From eb0581c2646fde09335615fbed8ed39641b096fc Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 14 Jan 2025 16:14:26 -0500 Subject: [PATCH 20/28] atsam: Add support for reporting canbus state Signed-off-by: Kevin O'Connor --- src/atsam/fdcan.c | 49 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/atsam/fdcan.c b/src/atsam/fdcan.c index deeb60706..20c86b443 100644 --- a/src/atsam/fdcan.c +++ b/src/atsam/fdcan.c @@ -1,11 +1,12 @@ // CANbus support on atsame70 chips // -// Copyright (C) 2021-2022 Kevin O'Connor +// Copyright (C) 2021-2025 Kevin O'Connor // Copyright (C) 2019 Eug Krashtan // Copyright (C) 2020 Pontus Borg // // This file may be distributed under the terms of the GNU GPLv3 license. +#include "board/irq.h" // irq_save #include "command.h" // DECL_CONSTANT_STR #include "generic/armcm_boot.h" // armcm_enable_irq #include "generic/canbus.h" // canbus_notify_tx @@ -147,6 +148,38 @@ canhw_set_filter(uint32_t id) CANx->MCAN_CCCR &= ~MCAN_CCCR_INIT; } +static struct { + uint32_t rx_error, tx_error; +} CAN_Errors; + +// Report interface status +void +canhw_get_status(struct canbus_status *status) +{ + irqstatus_t flag = irq_save(); + uint32_t psr = CANx->MCAN_PSR, lec = psr & MCAN_PSR_LEC_Msk; + if (lec && lec != 7) { + // Reading PSR clears it - so update state here + if (lec >= 3 && lec <= 5) + CAN_Errors.tx_error += 1; + else + CAN_Errors.rx_error += 1; + } + uint32_t rx_error = CAN_Errors.rx_error, tx_error = CAN_Errors.tx_error; + irq_restore(flag); + + status->rx_error = rx_error; + status->tx_error = tx_error; + if (psr & MCAN_PSR_BO) + status->bus_state = CANBUS_STATE_OFF; + else if (psr & MCAN_PSR_EP) + status->bus_state = CANBUS_STATE_PASSIVE; + else if (psr & MCAN_PSR_EW) + status->bus_state = CANBUS_STATE_WARN; + else + status->bus_state = 0; +} + // This function handles CAN global interrupts void CAN_IRQHandler(void) @@ -183,6 +216,18 @@ CAN_IRQHandler(void) CANx->MCAN_IR = FDCAN_IE_TC; canbus_notify_tx(); } + if (ir & (MCAN_IR_PED | MCAN_IR_PEA)) { + // Bus error + uint32_t psr = CANx->MCAN_PSR; + CANx->MCAN_IR = MCAN_IR_PED | MCAN_IR_PEA; + uint32_t lec = psr & MCAN_PSR_LEC_Msk; + if (lec && lec != 7) { + if (lec >= 3 && lec <= 5) + CAN_Errors.tx_error += 1; + else + CAN_Errors.rx_error += 1; + } + } } static inline const uint32_t @@ -302,6 +347,6 @@ can_init(void) /*##-3- Configure Interrupts #################################*/ armcm_enable_irq(CAN_IRQHandler, CANx_IRQn, 1); CANx->MCAN_ILE = MCAN_ILE_EINT0; - CANx->MCAN_IE = MCAN_IE_RF0NE | FDCAN_IE_TC; + CANx->MCAN_IE = MCAN_IE_RF0NE | FDCAN_IE_TC | MCAN_IE_PEDE | MCAN_IE_PEAE; } DECL_INIT(can_init); From 2db2ef82f2c0ba08d7e72fbab355de0846272b3a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 14 Jan 2025 15:26:29 -0500 Subject: [PATCH 21/28] canbus_stats: Periodically report canbus interface statistics Add support for a new get_canbus_status command to canserial.c . Add new canbus_stats.py module that will periodically query canbus mcus for connection status information. Signed-off-by: Kevin O'Connor --- docs/Status_Reference.md | 21 +++++++++++ klippy/extras/canbus_stats.py | 68 +++++++++++++++++++++++++++++++++++ klippy/mcu.py | 1 + src/generic/canserial.c | 21 ++++++++++- 4 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 klippy/extras/canbus_stats.py diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index 66d840d16..ee8099025 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -39,6 +39,27 @@ the following strings: "adjust", "fine". - `current_screw`: The index for the current screw being adjusted. - `accepted_screws`: The number of accepted screws. +## canbus_stats + +The following information is available in the `canbus_stats +some_mcu_name` object (this object is automatically available if an +mcu is configured to use canbus): +- `rx_error`: The number of receive errors detected by the + micro-controller canbus hardware. +- `tx_error`: The number of transmit errors detected by the + micro-controller canbus hardware. +- `tx_retries`: The number of transmit attempts that were retried due + to bus contention or errors. +- `bus_state`: The status of the interface (typically "active" for a + bus in normal operation, "warn" for a bus with recent errors, + "passive" for a bus that will no longer transmit canbus error + frames, or "off" for a bus that will no longer transmit or receive + messages). + +Note that only the rp2XXX micro-controllers report a non-zero +`tx_retries` field and the rp2XXX micro-controllers always report +`tx_error` as zero and `bus_state` as "active". + ## configfile The following information is available in the `configfile` object diff --git a/klippy/extras/canbus_stats.py b/klippy/extras/canbus_stats.py new file mode 100644 index 000000000..b9ee31027 --- /dev/null +++ b/klippy/extras/canbus_stats.py @@ -0,0 +1,68 @@ +# Report canbus connection status +# +# Copyright (C) 2025 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. + +class PrinterCANBusStats: + def __init__(self, config): + self.printer = config.get_printer() + self.reactor = self.printer.get_reactor() + self.name = config.get_name().split()[-1] + self.mcu = None + self.get_canbus_status_cmd = None + self.status = {'rx_error': None, 'tx_error': None, 'tx_retries': None, + 'bus_state': None} + self.printer.register_event_handler("klippy:connect", + self.handle_connect) + self.printer.register_event_handler("klippy:shutdown", + self.handle_shutdown) + def handle_shutdown(self): + status = self.status.copy() + if status['bus_state'] is not None: + # Clear bus_state on shutdown to note that the values may be stale + status['bus_state'] = 'unknown' + self.status = status + def handle_connect(self): + # Lookup mcu + mcu_name = self.name + if mcu_name != 'mcu': + mcu_name = 'mcu ' + mcu_name + self.mcu = self.printer.lookup_object(mcu_name) + # Lookup status query command + if self.mcu.try_lookup_command("get_canbus_status") is None: + return + self.get_canbus_status_cmd = self.mcu.lookup_query_command( + "get_canbus_status", + "canbus_status rx_error=%u tx_error=%u tx_retries=%u" + " canbus_bus_state=%u") + # Register periodic query timer + self.reactor.register_timer(self.query_event, self.reactor.NOW) + def query_event(self, eventtime): + prev_rx = self.status['rx_error'] + prev_tx = self.status['tx_error'] + prev_retries = self.status['tx_retries'] + if prev_rx is None: + prev_rx = prev_tx = prev_retries = 0 + params = self.get_canbus_status_cmd.send() + rx = prev_rx + ((params['rx_error'] - prev_rx) & 0xffffffff) + tx = prev_tx + ((params['tx_error'] - prev_tx) & 0xffffffff) + retries = prev_retries + ((params['tx_retries'] - prev_retries) + & 0xffffffff) + state = params['canbus_bus_state'] + self.status = {'rx_error': rx, 'tx_error': tx, 'tx_retries': retries, + 'bus_state': state} + return self.reactor.monotonic() + 1. + def stats(self, eventtime): + status = self.status + if status['rx_error'] is None: + return (False, '') + return (False, 'canstat_%s: bus_state=%s rx_error=%d' + ' tx_error=%d tx_retries=%d' + % (self.name, status['bus_state'], status['rx_error'], + status['tx_error'], status['tx_retries'])) + def get_status(self, eventtime): + return self.status + +def load_config_prefix(config): + return PrinterCANBusStats(config) diff --git a/klippy/mcu.py b/klippy/mcu.py index eb71e6bc2..b12888f8c 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -565,6 +565,7 @@ class MCU: self._canbus_iface = config.get('canbus_interface', 'can0') cbid = self._printer.load_object(config, 'canbus_ids') cbid.add_uuid(config, canbus_uuid, self._canbus_iface) + self._printer.load_object(config, 'canbus_stats %s' % (self._name,)) else: self._serialport = config.get('serial') if not (self._serialport.startswith("/dev/rpmsg_") diff --git a/src/generic/canserial.c b/src/generic/canserial.c index d56235f57..b90eb6591 100644 --- a/src/generic/canserial.c +++ b/src/generic/canserial.c @@ -2,7 +2,7 @@ // // Copyright (C) 2019 Eug Krashtan // Copyright (C) 2020 Pontus Borg -// Copyright (C) 2021 Kevin O'Connor +// Copyright (C) 2021-2025 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -318,6 +318,25 @@ DECL_TASK(canserial_rx_task); * Setup and shutdown ****************************************************************/ +DECL_ENUMERATION("canbus_bus_state", "active", CANBUS_STATE_ACTIVE); +DECL_ENUMERATION("canbus_bus_state", "warn", CANBUS_STATE_WARN); +DECL_ENUMERATION("canbus_bus_state", "passive", CANBUS_STATE_PASSIVE); +DECL_ENUMERATION("canbus_bus_state", "off", CANBUS_STATE_OFF); + +void +command_get_canbus_status(uint32_t *args) +{ + struct canbus_status status; + memset(&status, 0, sizeof(status)); + canhw_get_status(&status); + sendf("canbus_status rx_error=%u tx_error=%u tx_retries=%u" + " canbus_bus_state=%u" + , status.rx_error, status.tx_error, status.tx_retries + , status.bus_state); +} +DECL_COMMAND_FLAGS(command_get_canbus_status, HF_IN_SHUTDOWN + , "get_canbus_status"); + void command_get_canbus_id(uint32_t *args) { From 2c90c97ccd5ceb9b4071f95866160344f013a86f Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 14 Jan 2025 22:38:08 -0500 Subject: [PATCH 22/28] usb_canbus: Detect canbus stalls when in usb to canbus bridge mode If the low-level canbus stops working then it could become impossible to send messages to and from the canbus bridge node itself. This can make it difficult to diagnose canbus problems. Change the canbus bridge code to detect if message transmits become stalled for 50+ milliseconds and go into a "discarding" state. In this discarding state, messages destined for the canbus will be discarded until the canbus becomes active again. In this discarding state it will therefore be possible to transmit messages to and from the canbus bridge node. Signed-off-by: Kevin O'Connor --- klippy/extras/canbus_stats.py | 12 +++++++ src/generic/usb_canbus.c | 60 +++++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/klippy/extras/canbus_stats.py b/klippy/extras/canbus_stats.py index b9ee31027..0ddafaf36 100644 --- a/klippy/extras/canbus_stats.py +++ b/klippy/extras/canbus_stats.py @@ -3,6 +3,7 @@ # Copyright (C) 2025 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. +import logging class PrinterCANBusStats: def __init__(self, config): @@ -36,8 +37,19 @@ class PrinterCANBusStats: "get_canbus_status", "canbus_status rx_error=%u tx_error=%u tx_retries=%u" " canbus_bus_state=%u") + # Register usb_canbus_state message handling (for usb to canbus bridge) + self.mcu.register_response(self.handle_usb_canbus_state, + "usb_canbus_state") # Register periodic query timer self.reactor.register_timer(self.query_event, self.reactor.NOW) + def handle_usb_canbus_state(self, params): + discard = params['discard'] + if discard: + logging.warning("USB CANBUS bridge '%s' is discarding!" + % (self.name,)) + else: + logging.warning("USB CANBUS bridge '%s' is no longer discarding." + % (self.name,)) def query_event(self, eventtime): prev_rx = self.status['rx_error'] prev_tx = self.status['tx_error'] diff --git a/src/generic/usb_canbus.c b/src/generic/usb_canbus.c index 9c2893bf2..25d41c1be 100644 --- a/src/generic/usb_canbus.c +++ b/src/generic/usb_canbus.c @@ -1,6 +1,6 @@ // Support for Linux "gs_usb" CANbus adapter emulation // -// Copyright (C) 2018-2022 Kevin O'Connor +// Copyright (C) 2018-2025 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -108,6 +108,10 @@ static struct usbcan_data { uint8_t notify_local, usb_send_busy; uint32_t assigned_id; + // State tracking for messages to be sent from host to canbus + uint32_t bus_send_discard_time; + uint8_t bus_send_state; + // Canbus data from host uint8_t host_status; uint32_t host_pull_pos, host_push_pos; @@ -118,6 +122,10 @@ static struct usbcan_data { struct canbus_msg canhw_queue[32]; } UsbCan; +enum { + BSS_READY = 0, BSS_BLOCKING, BSS_DISCARDING +}; + enum { HS_TX_ECHO = 1, HS_TX_HW = 2, @@ -205,10 +213,56 @@ fill_usb_host_queue(void) } } +// Report bus stall state +static void +note_discard_state(uint32_t discard) +{ + sendf("usb_canbus_state discard=%u", discard); +} + +// Check if canbus queue has gotten stuck +static int +check_need_discard(void) +{ + if (UsbCan.bus_send_state != BSS_BLOCKING) + return 0; + return timer_is_before(UsbCan.bus_send_discard_time, timer_read_time()); +} + +// Attempt to send a message on the canbus +static int +try_canmsg_send(struct canbus_msg *msg) +{ + int ret = canhw_send(msg); + if (ret >= 0) { + // Success + if (UsbCan.bus_send_state == BSS_DISCARDING) + note_discard_state(0); + UsbCan.bus_send_state = BSS_READY; + return ret; + } + + // Unable to send message + if (check_need_discard()) { + // The canbus is stalled - start discarding messages + note_discard_state(1); + UsbCan.bus_send_state = BSS_DISCARDING; + } + if (UsbCan.bus_send_state == BSS_DISCARDING) + // Queue is stalled - just discard the message + return 0; + if (UsbCan.bus_send_state == BSS_READY) { + // Just starting to block - setup stall detection after 50ms + UsbCan.bus_send_state = BSS_BLOCKING; + UsbCan.bus_send_discard_time = timer_read_time() + timer_from_us(50000); + } + return ret; +} + void usbcan_task(void) { - if (!sched_check_wake(&UsbCan.wake)) + if (!sched_check_wake(&UsbCan.wake) && !check_need_discard()) return; // Send any pending hw frames to host @@ -235,7 +289,7 @@ usbcan_task(void) UsbCan.host_status = host_status = host_status & ~HS_TX_LOCAL; } if (host_status & HS_TX_HW) { - int ret = canhw_send(&msg); + int ret = try_canmsg_send(&msg); if (ret < 0) break; UsbCan.host_status = host_status = host_status & ~HS_TX_HW; From 8a2de5f23e0ffdd2ca88b78d1a99217e5132256a Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Fri, 31 Jan 2025 11:00:16 +0000 Subject: [PATCH 23/28] save_variables: Check lowercase variable names Signed-off-by: Pedro Lamas --- docs/Config_Changes.md | 4 ++++ docs/G-Codes.md | 5 +++-- klippy/extras/save_variables.py | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index a73a0c37f..adc03a082 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,10 @@ All dates in this document are approximate. ## Changes +20250131: Option `VARIABLE=` in `SAVE_VARIABLE` requires lowercase +value. For example, `extruder` instead of mixedcase `Extruder` or +uppercase `EXTRUDER`. Using any uppercase letter will raise an error. + 20241203: The resonance test has been changed to include slow sweeping moves. This change requires that testing point(s) have some clearance in X/Y plane (+/- 30 mm from the test point should suffice when using diff --git a/docs/G-Codes.md b/docs/G-Codes.md index ff6182faf..78c1c74b1 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -1201,8 +1201,9 @@ has been enabled. #### SAVE_VARIABLE `SAVE_VARIABLE VARIABLE= VALUE=`: Saves the variable to -disk so that it can be used across restarts. All stored variables are -loaded into the `printer.save_variables.variables` dict at startup and +disk so that it can be used across restarts. The VARIABLE must be lowercase. +All stored variables are loaded into the +`printer.save_variables.variables` dict at startup and can be used in gcode macros. The provided VALUE is parsed as a Python literal. diff --git a/klippy/extras/save_variables.py b/klippy/extras/save_variables.py index 6cedcf466..2e405657d 100644 --- a/klippy/extras/save_variables.py +++ b/klippy/extras/save_variables.py @@ -36,6 +36,8 @@ class SaveVariables: cmd_SAVE_VARIABLE_help = "Save arbitrary variables to disk" def cmd_SAVE_VARIABLE(self, gcmd): varname = gcmd.get('VARIABLE') + if (varname.lower() != varname): + raise gcmd.error("VARIABLE must not contain upper case") value = gcmd.get('VALUE') try: value = ast.literal_eval(value) From 638c085ffad94e5ad4b9b953e9bb37e40151bab2 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 2 Feb 2025 18:58:53 -0500 Subject: [PATCH 24/28] mkdocs-requirements: Update jinja dependency to 3.1.5 It appears there was a security vulnerability in Jinja v3.1.4 . The Klipper docs are not impacted by this vulnerability, but it's simple enough to increment the version to avoid warnings. Signed-off-by: Kevin O'Connor --- docs/_klipper3d/mkdocs-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_klipper3d/mkdocs-requirements.txt b/docs/_klipper3d/mkdocs-requirements.txt index 96bf60051..20f1071c3 100644 --- a/docs/_klipper3d/mkdocs-requirements.txt +++ b/docs/_klipper3d/mkdocs-requirements.txt @@ -1,5 +1,5 @@ # Python virtualenv module requirements for mkdocs -jinja2==3.1.4 +jinja2==3.1.5 mkdocs==1.2.4 mkdocs-material==8.1.3 mkdocs-simple-hooks==0.1.3 From 01b0e98ab21d505abcc5be83cca2b4ae1d0651eb Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 2 Feb 2025 20:51:18 -0500 Subject: [PATCH 25/28] klippy-requirements: Require setuptools on python 3.12 The python-can v3.3.4 package requires setuptools to be an explicit dependency when run on python v3.12, but there is no single version of setuptools that runs on all supported versions of python. So, tie setuptools to python versions 3.12 or later. Signed-off-by: Kevin O'Connor --- scripts/klippy-requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/klippy-requirements.txt b/scripts/klippy-requirements.txt index 7fb235404..87e664c6b 100644 --- a/scripts/klippy-requirements.txt +++ b/scripts/klippy-requirements.txt @@ -9,3 +9,4 @@ greenlet==3.0.3 ; python_version >= '3.12' Jinja2==2.11.3 python-can==3.3.4 markupsafe==1.1.1 +setuptools==75.6.0 ; python_version >= '3.12' # Needed by python-can From 329fbd01d8bbe8b2316de11e5dac1b5862211e6d Mon Sep 17 00:00:00 2001 From: Auxon <62841108+roman-simanovich@users.noreply.github.com> Date: Tue, 4 Feb 2025 19:08:05 -0500 Subject: [PATCH 26/28] docs: Update Pressure_Advance.md (#6808) Added language to disable "scarf joint" seams as it messes with the TUNING_TOWER script. Signed-off-by: Roman Simanovich --- docs/Pressure_Advance.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Pressure_Advance.md b/docs/Pressure_Advance.md index 2f3601cab..1e48f26c6 100644 --- a/docs/Pressure_Advance.md +++ b/docs/Pressure_Advance.md @@ -22,7 +22,7 @@ Use a slicer to generate g-code for the large hollow square found in [docs/prints/square_tower.stl](prints/square_tower.stl). Use a high speed (eg, 100mm/s), zero infill, and a coarse layer height (the layer height should be around 75% of the nozzle diameter). Make sure any -"dynamic acceleration control" is disabled in the slicer. +"dynamic acceleration control" and "scarf joint" seams are disabled in the slicer. Prepare for the test by issuing the following G-Code command: ``` From b16cb6575d69aa4fd1737e42f19d28aec1fcad89 Mon Sep 17 00:00:00 2001 From: Lexi Beavil Date: Tue, 4 Feb 2025 21:12:44 +0000 Subject: [PATCH 27/28] docs: Fix broken link to MainsailOS The documentation install page has a link to the old mainsail OS site, which is no longer available Signed-off-by: Lexi Beavil --- docs/Installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Installation.md b/docs/Installation.md index ca64259aa..06550a028 100644 --- a/docs/Installation.md +++ b/docs/Installation.md @@ -61,7 +61,7 @@ their own Klipper-centric images. The two main Moonraker based front ends are [Fluidd](https://docs.fluidd.xyz/) and [Mainsail](https://docs.mainsail.xyz/), the latter of which has a premade install -image ["MainsailOS"](http://docs.mainsailOS.xyz), this has the option for Raspberry Pi +image ["MainsailOS"](https://docs-os.mainsail.xyz/), this has the option for Raspberry Pi and some OrangePi varianta. Fluidd can be installed via KIAUH(Klipper Install And Update Helper), which From fec3e685c92ef263a829a73510c74245d7772c03 Mon Sep 17 00:00:00 2001 From: Timofey Titovets Date: Thu, 30 Jan 2025 21:24:16 +0100 Subject: [PATCH 28/28] stm32: h7 spi support reload mode & frequency Signed-off-by: Timofey Titovets --- src/stm32/gpio.h | 8 +++++++- src/stm32/stm32h7_spi.c | 12 ++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/stm32/gpio.h b/src/stm32/gpio.h index 78f567b81..96105e9bc 100644 --- a/src/stm32/gpio.h +++ b/src/stm32/gpio.h @@ -38,7 +38,13 @@ void gpio_adc_cancel_sample(struct gpio_adc g); struct spi_config { void *spi; - uint32_t spi_cr1; + union { + uint32_t spi_cr1; + struct { + uint8_t div; + uint8_t mode; + }; + }; }; struct spi_config spi_setup(uint32_t bus, uint8_t mode, uint32_t rate); void spi_prepare(struct spi_config config); diff --git a/src/stm32/stm32h7_spi.c b/src/stm32/stm32h7_spi.c index ec69f851a..35f25e46a 100644 --- a/src/stm32/stm32h7_spi.c +++ b/src/stm32/stm32h7_spi.c @@ -100,19 +100,27 @@ spi_setup(uint32_t bus, uint8_t mode, uint32_t rate) while ((pclk >> (div + 1)) > rate && div < 7) div++; - uint32_t cr1 = SPI_CR1_SPE; spi->CFG1 |= (div << SPI_CFG1_MBR_Pos) | (7 << SPI_CFG1_DSIZE_Pos); CLEAR_BIT(spi->CFG1, SPI_CFG1_CRCSIZE); spi->CFG2 |= ((mode << SPI_CFG2_CPHA_Pos) | SPI_CFG2_MASTER | SPI_CFG2_SSM | SPI_CFG2_AFCNTR | SPI_CFG2_SSOE); spi->CR1 |= SPI_CR1_SSI; - return (struct spi_config){ .spi = spi, .spi_cr1 = cr1 }; + return (struct spi_config){ .spi = spi, .div = div, .mode = mode }; } void spi_prepare(struct spi_config config) { + uint32_t div = config.div; + uint32_t mode = config.mode; + SPI_TypeDef *spi = config.spi; + // Reload frequency + spi->CFG1 = (spi->CFG1 & ~SPI_CFG1_MBR_Msk); + spi->CFG1 |= (div << SPI_CFG1_MBR_Pos); + // Reload mode + spi->CFG2 = (spi->CFG2 & ~SPI_CFG2_CPHA_Msk); + spi->CFG2 |= (mode << SPI_CFG2_CPHA_Pos); } void