From 3d3b87f97fc91d93ae4198b36198262207e1202b Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 16 Jan 2024 20:40:55 -0500 Subject: [PATCH 001/190] toolhead: Ensure full kin_flush_delay after flush_step_generation() Commit b7b13588 made it possible that the kinematic code could be restarted after a flush_step_generation() call without a sufficient delay. Rename last_sg_flush_time to min_restart_time and use that to ensure _calc_print_time() always pauses kin_flush_delay time since the last flush_step_generation() call. Also, update force_move to invoke flush_step_generation() after any movements. This is needed to ensure there is a sufficient delay should force_move be called on a stepper motor that is part of the toolhead kinematics and is using a step generation "scan time". This fixes possible "internal error in stepcompress" reports when using FORCE_MOVE. Signed-off-by: Kevin O'Connor --- klippy/extras/force_move.py | 2 +- klippy/toolhead.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/klippy/extras/force_move.py b/klippy/extras/force_move.py index 7501ea986e2d..9cf495191a57 100644 --- a/klippy/extras/force_move.py +++ b/klippy/extras/force_move.py @@ -90,8 +90,8 @@ def manual_move(self, stepper, dist, speed, accel=0.): print_time + 99999.9) stepper.set_trapq(prev_trapq) stepper.set_stepper_kinematics(prev_sk) - toolhead.note_kinematic_activity(print_time) toolhead.dwell(accel_t + cruise_t + accel_t) + toolhead.flush_step_generation() def _lookup_stepper(self, gcmd): name = gcmd.get('STEPPER') if name not in self.steppers: diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 477b5fce08cc..55a39eff12c2 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -239,7 +239,7 @@ def __init__(self, config): # Flush tracking self.flush_timer = self.reactor.register_timer(self._flush_handler) self.do_kick_flush_timer = True - self.last_flush_time = self.last_sg_flush_time = 0. + self.last_flush_time = self.min_restart_time = 0. self.need_flush_time = self.step_gen_time = self.clear_history_time = 0. # Kinematic step generation scan window time tracking self.kin_flush_delay = SDS_CHECK_TIME @@ -289,7 +289,7 @@ def _advance_flush_time(self, flush_time): sg_flush_time = max(sg_flush_want, flush_time) for sg in self.step_generators: sg(sg_flush_time) - self.last_sg_flush_time = sg_flush_time + self.min_restart_time = max(self.min_restart_time, sg_flush_time) # Free trapq entries that are no longer needed clear_history_time = self.clear_history_time if not self.can_pause: @@ -314,7 +314,7 @@ def _advance_move_time(self, next_print_time): def _calc_print_time(self): curtime = self.reactor.monotonic() est_print_time = self.mcu.estimated_print_time(curtime) - kin_time = max(est_print_time + MIN_KIN_TIME, self.last_sg_flush_time) + kin_time = max(est_print_time + MIN_KIN_TIME, self.min_restart_time) kin_time += self.kin_flush_delay min_print_time = max(est_print_time + BUFFER_TIME_START, kin_time) if min_print_time > self.print_time: @@ -361,6 +361,7 @@ def _flush_lookahead(self): def flush_step_generation(self): self._flush_lookahead() self._advance_flush_time(self.step_gen_time) + self.min_restart_time = max(self.min_restart_time, self.print_time) def get_last_move_time(self): if self.special_queuing_state: self._flush_lookahead() From 7a74888b43a7e640a32fd18ae69d9dbdeaf55719 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 17 Jan 2024 11:22:16 -0500 Subject: [PATCH 002/190] toolhead: Extend flushing slightly past required time There is no harm in enabling flushing for a little longer than necessary. In contrast, a slight rounding issue causing a message to not get flushed properly could result in an error. So, extend the flushing time slightly to avoid potential issues. Signed-off-by: Kevin O'Connor --- klippy/extras/pwm_tool.py | 4 +--- klippy/toolhead.py | 8 +++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/klippy/extras/pwm_tool.py b/klippy/extras/pwm_tool.py index 5fc09eab9883..704266a857a2 100644 --- a/klippy/extras/pwm_tool.py +++ b/klippy/extras/pwm_tool.py @@ -6,7 +6,6 @@ import chelper MAX_SCHEDULE_TIME = 5.0 -CLOCK_SYNC_EXTRA_TIME = 0.050 class error(Exception): pass @@ -118,8 +117,7 @@ def _send_update(self, clock, val): # Continue flushing to resend time wakeclock += self._duration_ticks wake_print_time = self._mcu.clock_to_print_time(wakeclock) - self._toolhead.note_kinematic_activity(wake_print_time - + CLOCK_SYNC_EXTRA_TIME) + self._toolhead.note_kinematic_activity(wake_print_time) def set_pwm(self, print_time, value): clock = self._mcu.print_time_to_clock(print_time) if self._invert: diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 55a39eff12c2..0d609a4a530a 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -1,6 +1,6 @@ # Code for coordinating events on the printer toolhead # -# Copyright (C) 2016-2021 Kevin O'Connor +# Copyright (C) 2016-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging, importlib @@ -191,6 +191,7 @@ def add_move(self, move): BUFFER_TIME_START = 0.250 BGFLUSH_LOW_TIME = 0.200 BGFLUSH_BATCH_TIME = 0.200 +BGFLUSH_EXTRA_TIME = 0.250 MIN_KIN_TIME = 0.100 MOVE_BATCH_TIME = 0.500 STEPCOMPRESS_FLUSH_TIME = 0.050 @@ -428,14 +429,15 @@ def _flush_handler(self, eventtime): self.check_stall_time = self.print_time # In "NeedPrime"/"Priming" state - flush queues if needed while 1: - if self.last_flush_time >= self.need_flush_time: + end_flush = self.need_flush_time + BGFLUSH_EXTRA_TIME + if self.last_flush_time >= end_flush: self.do_kick_flush_timer = True return self.reactor.NEVER buffer_time = self.last_flush_time - est_print_time if buffer_time > BGFLUSH_LOW_TIME: return eventtime + buffer_time - BGFLUSH_LOW_TIME ftime = est_print_time + BGFLUSH_LOW_TIME + BGFLUSH_BATCH_TIME - self._advance_flush_time(min(self.need_flush_time, ftime)) + self._advance_flush_time(min(end_flush, ftime)) except: logging.exception("Exception in flush_handler") self.printer.invoke_shutdown("Exception in flush_handler") From 43d0dba4b48ae7a9fcdfec2dabf053440d908fa1 Mon Sep 17 00:00:00 2001 From: grnbrg Date: Thu, 18 Jan 2024 09:13:54 -0600 Subject: [PATCH 003/190] config: Add Creality Ender 5 S1. (#6455) Creality released the Ender 5 S1 model in November of 2022. It has enough hardware differences from the previous models that that the existing Ender 5 configs are not compatible. This configuration is based on one provided by Creality that was then tweaked and modified. I have been using these values (plus some additional entries) for about 6 months with no issues. Signed-off-by: Brian Greenberg --- config/printer-creality-ender5-s1-2023.cfg | 170 +++++++++++++++++++++ test/klippy/printers.test | 1 + 2 files changed, 171 insertions(+) create mode 100644 config/printer-creality-ender5-s1-2023.cfg diff --git a/config/printer-creality-ender5-s1-2023.cfg b/config/printer-creality-ender5-s1-2023.cfg new file mode 100644 index 000000000000..68a89fa5e3d3 --- /dev/null +++ b/config/printer-creality-ender5-s1-2023.cfg @@ -0,0 +1,170 @@ +# Creality Ender 5 S1 (HW version: CR4NS200141C13) +# +# printer_size: 220x220x280 +# To use this config, during "make menuconfig" select the STM32F401 +# with a "64KiB bootloader" and serial (on USART1 PA10/PA9) +# communication. +# +# Flash this firmware by creating a directory named "STM32F4_UPDATE" +# on an SD card, copying the "out/klipper.bin" to it and then turn +# on the printer with the card inserted. The firmware filename must +# end in ".bin" and must not match the last filename that was flashed. +# +# See docs/Config_Reference.md for a description of parameters. + +[stepper_x] +step_pin: PC2 +dir_pin: !PB9 +enable_pin: !PC3 +rotation_distance: 40 +microsteps: 16 +endstop_pin: !PA5 +position_endstop: 220 +position_max: 222 +homing_speed: 80 + +[stepper_y] +step_pin: PB8 +dir_pin: !PB7 +enable_pin: !PC3 +rotation_distance: 40 +microsteps: 16 +endstop_pin: !PA6 +position_endstop: 220 +position_max: 220 +homing_speed: 80 + +[stepper_z] +step_pin: PB6 +dir_pin: PB5 +enable_pin: !PC3 +rotation_distance: 8 +microsteps: 16 +endstop_pin: probe:z_virtual_endstop +position_max: 280 +homing_speed: 20 +second_homing_speed: 1 +homing_retract_dist: 2.0 + +[extruder] +step_pin: PB4 +dir_pin: PB3 +enable_pin: !PC3 +rotation_distance: 7.5 +microsteps: 16 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PA1 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC5 +control: pid # tuned for stock hardware with 210 degree Celsius target +pid_kp: 20.749 +pid_ki: 1.064 +pid_kd: 101.153 +min_temp: 0 +max_temp: 305 + +[heater_bed] +heater_pin: PA7 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC4 +control: pid # tuned for stock hardware with 60 degree Celsius target +pid_kp: 66.566 +pid_ki: 0.958 +pid_kd: 1155.761 +min_temp: 0 +max_temp: 110 + +# Part cooling fan +[fan] +pin: PA0 +kick_start_time: 0.5 + +# Hotend fan +# set fan runnig when extruder temperature is over 60 +[heater_fan heatbreak_fan] +pin: PC0 +heater:extruder +heater_temp: 60 +fan_speed: 0.8 + +[filament_switch_sensor filament_sensor] +pause_on_runout: true +switch_pin: ^!PC15 + +# Stock CR Touch bed sensor +[bltouch] +sensor_pin: ^PC14 +control_pin: PC13 +x_offset: -13 +y_offset: 27 +z_offset: 2.0 +speed: 10 +stow_on_each_sample: true # Occasional bed crashes when false +samples: 4 +sample_retract_dist: 2 +samples_result: average +probe_with_touch_mode: true + +[bed_mesh] +speed: 150 +mesh_min: 3,28 # need to handle head distance with bl_touch +mesh_max: 205,218 +mesh_pps: 3 +probe_count: 4,4 +fade_start: 1 +fade_end: 10 +fade_target: 0 + +[mcu] +serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0 +restart_method: command + +[safe_z_home] +home_xy_position: 123,83 +speed: 200 +z_hop: 10 +z_hop_speed: 10 + +# Many Ender 5 S1 printers appear to suffer from a slight twist +# in the X axis. This twist can be measured, and compensated for +# using the AXIS_TWIST_COMPENSATION_CALIBRATE G-Code command. See +# https://www.klipper3d.org/Axis_Twist_Compensation.html for more +# information. This section provides the setup for this optional +# calibration step. +[axis_twist_compensation] +calibrate_start_x: 3 +calibrate_end_x: 207 +calibrate_y: 110 + +# Probe locations for assisted bed screw adjustment. +[screws_tilt_adjust] +screw1: 38,6 +screw1_name: Front Left Screw +screw2: 215,6 +screw2_name: Front Right Screw +screw3: 215,175 +screw3_name: Rear Right Screw +screw4: 38,175 +screw4_name: Rear Left Screw +horizontal_move_z: 5 +speed: 100 +screw_thread: CW-M4 + +[bed_screws] +screw1: 25,25 +screw1_name: Front Left Screw +screw2: 195,25 +screw2_name: Front Right Screw +screw3: 195,195 +screw3_name: Rear Right Screw +screw4: 25,195 +screw4_name: Rear Left Screw + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 5000 +max_z_velocity: 5 +max_z_accel: 100 +square_corner_velocity: 5.0 diff --git a/test/klippy/printers.test b/test/klippy/printers.test index 8a876f002d75..94fe92c2724c 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -205,6 +205,7 @@ CONFIG ../../config/printer-voxelab-aquila-2021.cfg DICTIONARY stm32f401.dict CONFIG ../../config/generic-fysetc-cheetah-v2.0.cfg CONFIG ../../config/printer-artillery-sidewinder-x2-2022.cfg +CONFIG ../../config/printer-creality-ender5-s1-2023.cfg CONFIG ../../config/printer-elegoo-neptune3-pro-2023.cfg # Printers using the stm32f405 From d633ef2cfc6cd48e55f4c1ab5ae058d8adc5b970 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 18 Jan 2024 11:24:07 -0500 Subject: [PATCH 004/190] force_move: Fix missing call to note_kinematic_activity() Commit 3d3b87f9 incorrectly removed the call to note_kinematic_activity(). A call to toolhead.dwell() is not sufficient to wake up the mcu move queue flushing. The call to note_kinematic_activity() is needed for that. Signed-off-by: Kevin O'Connor --- klippy/extras/force_move.py | 1 + 1 file changed, 1 insertion(+) diff --git a/klippy/extras/force_move.py b/klippy/extras/force_move.py index 9cf495191a57..5f47f8e2e409 100644 --- a/klippy/extras/force_move.py +++ b/klippy/extras/force_move.py @@ -90,6 +90,7 @@ def manual_move(self, stepper, dist, speed, accel=0.): print_time + 99999.9) stepper.set_trapq(prev_trapq) stepper.set_stepper_kinematics(prev_sk) + toolhead.note_kinematic_activity(print_time) toolhead.dwell(accel_t + cruise_t + accel_t) toolhead.flush_step_generation() def _lookup_stepper(self, gcmd): From 6cc409f6fb9f62226c56adcf80be32a0d2601ab1 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 18 Jan 2024 12:16:47 -0500 Subject: [PATCH 005/190] toolhead: Rename MoveQueue class to LookAheadQueue Rename this class so that is is not confused with the mcu "move queue". Signed-off-by: Kevin O'Connor --- docs/Code_Overview.md | 36 +++++++++++++++++++----------------- klippy/toolhead.py | 28 ++++++++++++++-------------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/docs/Code_Overview.md b/docs/Code_Overview.md index 5b3e07e11da5..0e4836acf5ec 100644 --- a/docs/Code_Overview.md +++ b/docs/Code_Overview.md @@ -136,8 +136,9 @@ provides further information on the mechanics of moves. * The ToolHead class (in toolhead.py) handles "look-ahead" and tracks the timing of printing actions. The main codepath for a move is: - `ToolHead.move() -> MoveQueue.add_move() -> MoveQueue.flush() -> - Move.set_junction() -> ToolHead._process_moves()`. + `ToolHead.move() -> LookAheadQueue.add_move() -> + LookAheadQueue.flush() -> Move.set_junction() -> + ToolHead._process_moves()`. * ToolHead.move() creates a Move() object with the parameters of the move (in cartesian space and in units of seconds and millimeters). * The kinematics class is given the opportunity to audit each move @@ -146,10 +147,10 @@ provides further information on the mechanics of moves. may raise an error if the move is not valid. If check_move() completes successfully then the underlying kinematics must be able to handle the move. - * MoveQueue.add_move() places the move object on the "look-ahead" - queue. - * MoveQueue.flush() determines the start and end velocities of each - move. + * LookAheadQueue.add_move() places the move object on the + "look-ahead" queue. + * LookAheadQueue.flush() determines the start and end velocities of + each move. * Move.set_junction() implements the "trapezoid generator" on a move. The "trapezoid generator" breaks every move into three parts: a constant acceleration phase, followed by a constant velocity @@ -170,17 +171,18 @@ provides further information on the mechanics of moves. placed on a "trapezoid motion queue": `ToolHead._process_moves() -> trapq_append()` (in klippy/chelper/trapq.c). The step times are then generated: `ToolHead._process_moves() -> - ToolHead._update_move_time() -> MCU_Stepper.generate_steps() -> - itersolve_generate_steps() -> itersolve_gen_steps_range()` (in - klippy/chelper/itersolve.c). The goal of the iterative solver is to - find step times given a function that calculates a stepper position - from a time. This is done by repeatedly "guessing" various times - until the stepper position formula returns the desired position of - the next step on the stepper. The feedback produced from each guess - is used to improve future guesses so that the process rapidly - converges to the desired time. The kinematic stepper position - formulas are located in the klippy/chelper/ directory (eg, - kin_cart.c, kin_corexy.c, kin_delta.c, kin_extruder.c). + ToolHead._advance_move_time() -> ToolHead._advance_flush_time() -> + MCU_Stepper.generate_steps() -> itersolve_generate_steps() -> + itersolve_gen_steps_range()` (in klippy/chelper/itersolve.c). The + goal of the iterative solver is to find step times given a function + that calculates a stepper position from a time. This is done by + repeatedly "guessing" various times until the stepper position + formula returns the desired position of the next step on the + stepper. The feedback produced from each guess is used to improve + future guesses so that the process rapidly converges to the desired + time. The kinematic stepper position formulas are located in the + klippy/chelper/ directory (eg, kin_cart.c, kin_corexy.c, + kin_delta.c, kin_extruder.c). * Note that the extruder is handled in its own kinematic class: `ToolHead._process_moves() -> PrinterExtruder.move()`. Since diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 0d609a4a530a..64189ca21753 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -110,7 +110,7 @@ def set_junction(self, start_v2, cruise_v2, end_v2): # Class to track a list of pending move requests and to facilitate # "look-ahead" across moves to reduce acceleration between moves. -class MoveQueue: +class LookAheadQueue: def __init__(self, toolhead): self.toolhead = toolhead self.queue = [] @@ -211,8 +211,8 @@ def __init__(self, config): self.all_mcus = [ m for n, m in self.printer.lookup_objects(module='mcu')] self.mcu = self.all_mcus[0] - self.move_queue = MoveQueue(self) - self.move_queue.set_flush_time(BUFFER_TIME_HIGH) + self.lookahead = LookAheadQueue(self) + self.lookahead.set_flush_time(BUFFER_TIME_HIGH) self.commanded_pos = [0., 0., 0., 0.] # Velocity and acceleration control self.max_velocity = config.getfloat('max_velocity', above=0.) @@ -354,10 +354,10 @@ def _process_moves(self, moves): self._advance_move_time(next_move_time) def _flush_lookahead(self): # Transit from "NeedPrime"/"Priming"/"Drip"/main state to "NeedPrime" - self.move_queue.flush() + self.lookahead.flush() self.special_queuing_state = "NeedPrime" self.need_check_pause = -1. - self.move_queue.set_flush_time(BUFFER_TIME_HIGH) + self.lookahead.set_flush_time(BUFFER_TIME_HIGH) self.check_stall_time = 0. def flush_step_generation(self): self._flush_lookahead() @@ -368,7 +368,7 @@ def get_last_move_time(self): self._flush_lookahead() self._calc_print_time() else: - self.move_queue.flush() + self.lookahead.flush() return self.print_time def _check_pause(self): eventtime = self.reactor.monotonic() @@ -462,7 +462,7 @@ def move(self, newpos, speed): if move.axes_d[3]: self.extruder.check_move(move) self.commanded_pos[:] = move.end_pos - self.move_queue.add_move(move) + self.lookahead.add_move(move) if self.print_time > self.need_check_pause: self._check_pause() def manual_move(self, coord, speed): @@ -509,12 +509,12 @@ def _update_drip_move_time(self, next_print_time): def drip_move(self, newpos, speed, drip_completion): self.dwell(self.kin_flush_delay) # Transition from "NeedPrime"/"Priming"/main state to "Drip" state - self.move_queue.flush() + self.lookahead.flush() self.special_queuing_state = "Drip" self.need_check_pause = self.reactor.NEVER self.reactor.update_timer(self.flush_timer, self.reactor.NEVER) self.do_kick_flush_timer = False - self.move_queue.set_flush_time(BUFFER_TIME_HIGH) + self.lookahead.set_flush_time(BUFFER_TIME_HIGH) self.check_stall_time = 0. self.drip_completion = drip_completion # Submit move @@ -526,9 +526,9 @@ def drip_move(self, newpos, speed, drip_completion): raise # Transmit move in "drip" mode try: - self.move_queue.flush() + self.lookahead.flush() except DripModeEndSignal as e: - self.move_queue.reset() + self.lookahead.reset() self.trapq_finalize_moves(self.trapq, self.reactor.NEVER, 0) # Exit "Drip" state self.reactor.update_timer(self.flush_timer, self.reactor.NOW) @@ -548,7 +548,7 @@ def stats(self, eventtime): self.print_time, max(buffer_time, 0.), self.print_stall) def check_busy(self, eventtime): est_print_time = self.mcu.estimated_print_time(eventtime) - lookahead_empty = not self.move_queue.queue + lookahead_empty = not self.lookahead.queue return self.print_time, est_print_time, lookahead_empty def get_status(self, eventtime): print_time = self.print_time @@ -566,7 +566,7 @@ def get_status(self, eventtime): return res def _handle_shutdown(self): self.can_pause = False - self.move_queue.reset() + self.lookahead.reset() def get_kinematics(self): return self.kin def get_trapq(self): @@ -583,7 +583,7 @@ def note_step_generation_scan_time(self, delay, old_delay=0.): new_delay = max(self.kin_flush_times + [SDS_CHECK_TIME]) self.kin_flush_delay = new_delay def register_lookahead_callback(self, callback): - last_move = self.move_queue.get_last() + last_move = self.lookahead.get_last() if last_move is None: callback(self.get_last_move_time()) return From 1d92be71da90b7d1e7fba0392fd23b506e093bf6 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 18 Jan 2024 12:20:44 -0500 Subject: [PATCH 006/190] toolhead: Rename note_kinematic_activity() to note_mcu_movequeue_activity() Rename this function to make it more clear why it is called. Signed-off-by: Kevin O'Connor --- klippy/extras/force_move.py | 2 +- klippy/extras/manual_stepper.py | 2 +- klippy/extras/pwm_tool.py | 2 +- klippy/toolhead.py | 14 +++++++------- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/klippy/extras/force_move.py b/klippy/extras/force_move.py index 5f47f8e2e409..50b801412dbd 100644 --- a/klippy/extras/force_move.py +++ b/klippy/extras/force_move.py @@ -90,7 +90,7 @@ def manual_move(self, stepper, dist, speed, accel=0.): print_time + 99999.9) stepper.set_trapq(prev_trapq) stepper.set_stepper_kinematics(prev_sk) - toolhead.note_kinematic_activity(print_time) + toolhead.note_mcu_movequeue_activity(print_time) toolhead.dwell(accel_t + cruise_t + accel_t) toolhead.flush_step_generation() def _lookup_stepper(self, gcmd): diff --git a/klippy/extras/manual_stepper.py b/klippy/extras/manual_stepper.py index 9f61e0298571..40db4a50316e 100644 --- a/klippy/extras/manual_stepper.py +++ b/klippy/extras/manual_stepper.py @@ -70,7 +70,7 @@ def do_move(self, movepos, speed, accel, sync=True): self.trapq_finalize_moves(self.trapq, self.next_cmd_time + 99999.9, self.next_cmd_time + 99999.9) toolhead = self.printer.lookup_object('toolhead') - toolhead.note_kinematic_activity(self.next_cmd_time) + toolhead.note_mcu_movequeue_activity(self.next_cmd_time) if sync: self.sync_print_time() def do_homing_move(self, movepos, speed, accel, triggered, check_trigger): diff --git a/klippy/extras/pwm_tool.py b/klippy/extras/pwm_tool.py index 704266a857a2..aa95ecbef1cc 100644 --- a/klippy/extras/pwm_tool.py +++ b/klippy/extras/pwm_tool.py @@ -117,7 +117,7 @@ def _send_update(self, clock, val): # Continue flushing to resend time wakeclock += self._duration_ticks wake_print_time = self._mcu.clock_to_print_time(wakeclock) - self._toolhead.note_kinematic_activity(wake_print_time) + self._toolhead.note_mcu_movequeue_activity(wake_print_time) def set_pwm(self, print_time, value): clock = self._mcu.print_time_to_clock(print_time) if self._invert: diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 64189ca21753..ce014365c6f9 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -349,8 +349,8 @@ def _process_moves(self, moves): # Generate steps for moves if self.special_queuing_state: self._update_drip_move_time(next_move_time) - self.note_kinematic_activity(next_move_time + self.kin_flush_delay, - set_step_gen_time=True) + self.note_mcu_movequeue_activity(next_move_time + self.kin_flush_delay, + set_step_gen_time=True) self._advance_move_time(next_move_time) def _flush_lookahead(self): # Transit from "NeedPrime"/"Priming"/"Drip"/main state to "NeedPrime" @@ -503,8 +503,8 @@ def _update_drip_move_time(self, next_print_time): self.drip_completion.wait(curtime + wait_time) continue npt = min(self.print_time + DRIP_SEGMENT_TIME, next_print_time) - self.note_kinematic_activity(npt + self.kin_flush_delay, - set_step_gen_time=True) + self.note_mcu_movequeue_activity(npt + self.kin_flush_delay, + set_step_gen_time=True) self._advance_move_time(npt) def drip_move(self, newpos, speed, drip_completion): self.dwell(self.kin_flush_delay) @@ -588,10 +588,10 @@ def register_lookahead_callback(self, callback): callback(self.get_last_move_time()) return last_move.timing_callbacks.append(callback) - def note_kinematic_activity(self, kin_time, set_step_gen_time=False): - self.need_flush_time = max(self.need_flush_time, kin_time) + def note_mcu_movequeue_activity(self, mq_time, set_step_gen_time=False): + self.need_flush_time = max(self.need_flush_time, mq_time) if set_step_gen_time: - self.step_gen_time = max(self.step_gen_time, kin_time) + self.step_gen_time = max(self.step_gen_time, mq_time) if self.do_kick_flush_timer: self.do_kick_flush_timer = False self.reactor.update_timer(self.flush_timer, self.reactor.NOW) From 94719fe327874d57ee92610c90972f74170a7b40 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 18 Jan 2024 13:36:17 -0500 Subject: [PATCH 007/190] docs: Update to mkdocs to use latest jinj2 version There is a jinja2 security advisory on the current Jinja2 version. Klipper is not impacted by this advisory (as it does not run jinja2 on any untrusted data), but there is no harm in updating. Signed-off-by: Kevin O'Connor --- docs/_klipper3d/mkdocs-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_klipper3d/mkdocs-requirements.txt b/docs/_klipper3d/mkdocs-requirements.txt index 9ea6d21924fe..7392889594fd 100644 --- a/docs/_klipper3d/mkdocs-requirements.txt +++ b/docs/_klipper3d/mkdocs-requirements.txt @@ -1,6 +1,6 @@ # Python virtualenv module requirements for mkdocs -jinja2==3.0.3 -mkdocs==1.2.3 +jinja2==3.1.3 +mkdocs==1.2.4 mkdocs-material==8.1.3 mkdocs-simple-hooks==0.1.3 mkdocs-exclude==1.0.2 From 83d0d2f19b81550e82d6f68415dfcd6df90dee69 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 17 Dec 2023 11:21:01 -0500 Subject: [PATCH 008/190] mcu: Add send_wait_ack() support to CommandWrapper Signed-off-by: Kevin O'Connor --- klippy/mcu.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/klippy/mcu.py b/klippy/mcu.py index 6318ff7f57a4..cfc389e76912 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -91,6 +91,9 @@ def __init__(self, serial, msgformat, cmd_queue=None): def send(self, data=(), minclock=0, reqclock=0): cmd = self._cmd.encode(data) self._serial.raw_send(cmd, minclock, reqclock, self._cmd_queue) + def send_wait_ack(self, data=(), minclock=0, reqclock=0): + cmd = self._cmd.encode(data) + self._serial.raw_send_wait_ack(cmd, minclock, reqclock, self._cmd_queue) def get_command_tag(self): return self._msgtag From 3275614b895c409f30cc9520c02990b18abad3c6 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 17 Dec 2023 11:30:23 -0500 Subject: [PATCH 009/190] sensor_adxl345: No need to send messages when stopping queries Simplify the mcu code as any messages are ignored by the host anyway. Signed-off-by: Kevin O'Connor --- klippy/extras/adxl345.py | 8 ++------ src/sensor_adxl345.c | 18 ------------------ 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index 8f40c7fec9a6..76fd4ca45f67 100644 --- a/klippy/extras/adxl345.py +++ b/klippy/extras/adxl345.py @@ -204,7 +204,7 @@ def __init__(self, config): self.spi = bus.MCU_SPI_from_config(config, 3, default_speed=5000000) self.mcu = mcu = self.spi.get_mcu() self.oid = oid = mcu.create_oid() - self.query_adxl345_cmd = self.query_adxl345_end_cmd = None + self.query_adxl345_cmd = None self.query_adxl345_status_cmd = None mcu.add_config_cmd("config_adxl345 oid=%d spi_oid=%d" % (oid, self.spi.get_oid())) @@ -230,10 +230,6 @@ def _build_config(self): cmdqueue = self.spi.get_command_queue() self.query_adxl345_cmd = self.mcu.lookup_command( "query_adxl345 oid=%c clock=%u rest_ticks=%u", cq=cmdqueue) - self.query_adxl345_end_cmd = self.mcu.lookup_query_command( - "query_adxl345 oid=%c clock=%u rest_ticks=%u", - "adxl345_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" - " buffered=%c fifo=%c limit_count=%hu", oid=self.oid, cq=cmdqueue) self.query_adxl345_status_cmd = self.mcu.lookup_query_command( "query_adxl345_status oid=%c", "adxl345_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" @@ -334,7 +330,7 @@ def _start_measurements(self): self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading - params = self.query_adxl345_end_cmd.send([self.oid, 0, 0]) + self.query_adxl345_cmd.send_wait_ack([self.oid, 0, 0]) self.bulk_queue.clear_samples() logging.info("ADXL345 finished '%s' measurements", self.name) def _process_batch(self, eventtime): diff --git a/src/sensor_adxl345.c b/src/sensor_adxl345.c index 3d80059d0f93..5ec3945e82cf 100644 --- a/src/sensor_adxl345.c +++ b/src/sensor_adxl345.c @@ -148,25 +148,7 @@ adxl_stop(struct adxl345 *ax, uint8_t oid) sched_del_timer(&ax->timer); ax->flags = 0; uint8_t msg[2] = { AR_POWER_CTL, 0x00 }; - uint32_t end1_time = timer_read_time(); spidev_transfer(ax->spi, 0, sizeof(msg), msg); - uint32_t end2_time = timer_read_time(); - // Drain any measurements still in fifo - uint_fast8_t i; - for (i=0; i<33; i++) { - msg[0] = AR_FIFO_STATUS | AM_READ; - msg[1] = 0x00; - spidev_transfer(ax->spi, 1, sizeof(msg), msg); - uint_fast8_t fifo_status = msg[1] & ~0x80; - if (!fifo_status) - break; - if (fifo_status <= 32) - adxl_query(ax, oid); - } - // Report final data - if (ax->data_count) - adxl_report(ax, oid); - adxl_status(ax, oid, end1_time, end2_time, msg[1]); } void From 5ff555a705b8d3a586f240eb7456b591ee928ac2 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 17 Dec 2023 11:31:58 -0500 Subject: [PATCH 010/190] sensor_mpu9250: No need to send messages when stopping queries Simplify the mcu code as any messages are ignored by the host anyway. Signed-off-by: Kevin O'Connor --- klippy/extras/mpu9250.py | 8 ++------ src/sensor_mpu9250.c | 9 --------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/klippy/extras/mpu9250.py b/klippy/extras/mpu9250.py index 04a33eb243a1..41376dc35d56 100644 --- a/klippy/extras/mpu9250.py +++ b/klippy/extras/mpu9250.py @@ -69,7 +69,7 @@ def __init__(self, config): default_speed=400000) self.mcu = mcu = self.i2c.get_mcu() self.oid = oid = mcu.create_oid() - self.query_mpu9250_cmd = self.query_mpu9250_end_cmd = None + self.query_mpu9250_cmd = None self.query_mpu9250_status_cmd = None mcu.register_config_callback(self._build_config) self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, "mpu9250_data", oid) @@ -95,10 +95,6 @@ def _build_config(self): % (self.oid,), on_restart=True) self.query_mpu9250_cmd = self.mcu.lookup_command( "query_mpu9250 oid=%c clock=%u rest_ticks=%u", cq=cmdqueue) - self.query_mpu9250_end_cmd = self.mcu.lookup_query_command( - "query_mpu9250 oid=%c clock=%u rest_ticks=%u", - "mpu9250_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" - " buffered=%c fifo=%u limit_count=%hu", oid=self.oid, cq=cmdqueue) self.query_mpu9250_status_cmd = self.mcu.lookup_query_command( "query_mpu9250_status oid=%c", "mpu9250_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" @@ -193,7 +189,7 @@ def _start_measurements(self): self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading - params = self.query_mpu9250_end_cmd.send([self.oid, 0, 0]) + self.query_mpu9250_cmd.send_wait_ack([self.oid, 0, 0]) self.bulk_queue.clear_samples() logging.info("MPU9250 finished '%s' measurements", self.name) self.set_reg(REG_PWR_MGMT_1, SET_PWR_MGMT_1_SLEEP) diff --git a/src/sensor_mpu9250.c b/src/sensor_mpu9250.c index c535d0971dc4..7792b4d802b0 100644 --- a/src/sensor_mpu9250.c +++ b/src/sensor_mpu9250.c @@ -192,16 +192,7 @@ mp9250_stop(struct mpu9250 *mp, uint8_t oid) // disable accel FIFO uint8_t msg[2] = { AR_FIFO_EN, SET_DISABLE_FIFO }; - uint32_t end1_time = timer_read_time(); i2c_write(mp->i2c->i2c_config, sizeof(msg), msg); - uint32_t end2_time = timer_read_time(); - - // Report final data - if (mp->data_count > 0) - mp9250_report(mp, oid); - uint16_t bytes_to_read = get_fifo_status(mp); - mp9250_status(mp, oid, end1_time, end2_time, - bytes_to_read / BYTES_PER_FIFO_ENTRY); // Uncomment and rebuilt to check for FIFO overruns when tuning //output("mpu9240 limit_count=%u fifo_max=%u", From 95e1a290f125650f5131a960d685d53a1e2f5e4f Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 17 Dec 2023 11:33:11 -0500 Subject: [PATCH 011/190] sensor_lis2dw: No need to send messages when stopping queries Simplify the mcu code as any messages are ignored by the host anyway. Signed-off-by: Kevin O'Connor --- klippy/extras/lis2dw.py | 8 ++------ src/sensor_lis2dw.c | 21 ++------------------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/klippy/extras/lis2dw.py b/klippy/extras/lis2dw.py index 28591c21b1b4..a7fe54d743f7 100644 --- a/klippy/extras/lis2dw.py +++ b/klippy/extras/lis2dw.py @@ -48,7 +48,7 @@ def __init__(self, config): self.spi = bus.MCU_SPI_from_config(config, 3, default_speed=5000000) self.mcu = mcu = self.spi.get_mcu() self.oid = oid = mcu.create_oid() - self.query_lis2dw_cmd = self.query_lis2dw_end_cmd = None + self.query_lis2dw_cmd = None self.query_lis2dw_status_cmd = None mcu.add_config_cmd("config_lis2dw oid=%d spi_oid=%d" % (oid, self.spi.get_oid())) @@ -75,10 +75,6 @@ def _build_config(self): cmdqueue = self.spi.get_command_queue() self.query_lis2dw_cmd = self.mcu.lookup_command( "query_lis2dw oid=%c clock=%u rest_ticks=%u", cq=cmdqueue) - self.query_lis2dw_end_cmd = self.mcu.lookup_query_command( - "query_lis2dw oid=%c clock=%u rest_ticks=%u", - "lis2dw_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" - " buffered=%c fifo=%c limit_count=%hu", oid=self.oid, cq=cmdqueue) self.query_lis2dw_status_cmd = self.mcu.lookup_query_command( "query_lis2dw_status oid=%c", "lis2dw_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" @@ -179,7 +175,7 @@ def _start_measurements(self): self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading - params = self.query_lis2dw_end_cmd.send([self.oid, 0, 0]) + self.query_lis2dw_cmd.send_wait_ack([self.oid, 0, 0]) self.bulk_queue.clear_samples() logging.info("LIS2DW finished '%s' measurements", self.name) self.set_reg(REG_LIS2DW_FIFO_CTRL, 0x00) diff --git a/src/sensor_lis2dw.c b/src/sensor_lis2dw.c index 52612623f085..06dd32068b34 100644 --- a/src/sensor_lis2dw.c +++ b/src/sensor_lis2dw.c @@ -23,7 +23,7 @@ struct lis2dw { uint32_t rest_ticks; struct spidev_s *spi; uint16_t sequence, limit_count; - uint8_t flags, data_count, fifo_disable; + uint8_t flags, data_count; uint8_t data[48]; }; @@ -117,7 +117,7 @@ lis2dw_query(struct lis2dw *ax, uint8_t oid) ax->limit_count++; // check if we need to run the task again (more packets in fifo?) - if (!fifo_empty&&!(ax->fifo_disable)) { + if (!fifo_empty) { // More data in fifo - wake this task again sched_wake_task(&lis2dw_wake); } else if (ax->flags & LIS_RUNNING) { @@ -134,7 +134,6 @@ lis2dw_start(struct lis2dw *ax, uint8_t oid) { sched_del_timer(&ax->timer); ax->flags = LIS_RUNNING; - ax->fifo_disable = 0; uint8_t ctrl[2] = {LIS_FIFO_CTRL , 0xC0}; spidev_transfer(ax->spi, 0, sizeof(ctrl), ctrl); lis2dw_reschedule_timer(ax); @@ -147,23 +146,8 @@ lis2dw_stop(struct lis2dw *ax, uint8_t oid) // Disable measurements sched_del_timer(&ax->timer); ax->flags = 0; - // Drain any measurements still in fifo - ax->fifo_disable = 1; - lis2dw_query(ax, oid); - uint8_t ctrl[2] = {LIS_FIFO_CTRL , 0}; - uint32_t end1_time = timer_read_time(); spidev_transfer(ax->spi, 0, sizeof(ctrl), ctrl); - uint32_t end2_time = timer_read_time(); - - uint8_t msg[2] = { LIS_FIFO_SAMPLES | LIS_AM_READ , 0}; - spidev_transfer(ax->spi, 1, sizeof(msg), msg); - uint8_t fifo_status = msg[1]&0x1f; - - //Report final data - if (ax->data_count) - lis2dw_report(ax, oid); - lis2dw_status(ax, oid, end1_time, end2_time, fifo_status); } void @@ -183,7 +167,6 @@ command_query_lis2dw(uint32_t *args) ax->flags = LIS_HAVE_START; ax->sequence = ax->limit_count = 0; ax->data_count = 0; - ax->fifo_disable = 0; sched_add_timer(&ax->timer); } DECL_COMMAND(command_query_lis2dw, From dc6182f3b339b990c8a68940f02a210e332be269 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 17 Dec 2023 11:35:40 -0500 Subject: [PATCH 012/190] sensor_angle: No need to send messages when stopping queries Simplify the mcu code as any messages are ignored by the host anyway. Signed-off-by: Kevin O'Connor --- klippy/extras/angle.py | 7 ++----- src/sensor_angle.c | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/klippy/extras/angle.py b/klippy/extras/angle.py index b1aa0d9679ae..163168d0b6e8 100644 --- a/klippy/extras/angle.py +++ b/klippy/extras/angle.py @@ -437,7 +437,7 @@ def __init__(self, config): self.oid = oid = mcu.create_oid() self.sensor_helper = sensor_class(config, self.spi, oid) # Setup mcu sensor_spi_angle bulk query code - self.query_spi_angle_cmd = self.query_spi_angle_end_cmd = None + self.query_spi_angle_cmd = None mcu.add_config_cmd( "config_spi_angle oid=%d spi_oid=%d spi_angle_type=%s" % (oid, self.spi.get_oid(), sensor_type)) @@ -462,9 +462,6 @@ def _build_config(self): self.query_spi_angle_cmd = self.mcu.lookup_command( "query_spi_angle oid=%c clock=%u rest_ticks=%u time_shift=%c", cq=cmdqueue) - self.query_spi_angle_end_cmd = self.mcu.lookup_query_command( - "query_spi_angle oid=%c clock=%u rest_ticks=%u time_shift=%c", - "spi_angle_end oid=%c sequence=%hu", oid=self.oid, cq=cmdqueue) def get_status(self, eventtime=None): return {'temperature': self.sensor_helper.last_temperature} def add_client(self, client_cb): @@ -543,7 +540,7 @@ def _start_measurements(self): self.time_shift], reqclock=reqclock) def _finish_measurements(self): # Halt bulk reading - params = self.query_spi_angle_end_cmd.send([self.oid, 0, 0, 0]) + self.query_spi_angle_cmd.send_wait_ack([self.oid, 0, 0, 0]) self.bulk_queue.clear_samples() self.sensor_helper.last_temperature = None logging.info("Stopped angle '%s' measurements", self.name) diff --git a/src/sensor_angle.c b/src/sensor_angle.c index 4d35aadf1483..865670b8994c 100644 --- a/src/sensor_angle.c +++ b/src/sensor_angle.c @@ -230,13 +230,10 @@ command_query_spi_angle(uint32_t *args) sched_del_timer(&sa->timer); sa->flags = 0; - if (!args[2]) { + if (!args[2]) // End measurements - if (sa->data_count) - angle_report(sa, oid); - sendf("spi_angle_end oid=%c sequence=%hu", oid, sa->sequence); return; - } + // Start new measurements query sa->timer.waketime = args[1]; sa->rest_ticks = args[2]; From 266e96621c0133e1192bbaec5addb6bcf443a203 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 17 Dec 2023 17:59:25 -0500 Subject: [PATCH 013/190] sensor_bulk: New C file with helper code for sending bulk sensor measurements Refactor the low-level "bulk sensor" management code in the mcu. This updates the sensor_adxl345.c, sensor_mpu9250.c, sensor_lis2dw.c, and sensor_angle.c code to use the same "bulk sensor" messages. All of these sensors will now send "sensor_bulk_data" and "sensor_bulk_status" messages. Signed-off-by: Kevin O'Connor --- klippy/extras/adxl345.py | 28 +++++--------------- klippy/extras/angle.py | 4 +-- klippy/extras/bulk_sensor.py | 34 ++++++++++++++---------- klippy/extras/lis2dw.py | 21 +++++---------- klippy/extras/mpu9250.py | 22 +++++----------- src/Kconfig | 4 +++ src/Makefile | 3 ++- src/sensor_adxl345.c | 51 ++++++++++++++---------------------- src/sensor_angle.c | 33 +++++++++-------------- src/sensor_bulk.c | 38 +++++++++++++++++++++++++++ src/sensor_bulk.h | 15 +++++++++++ src/sensor_lis2dw.c | 45 +++++++++---------------------- src/sensor_mpu9250.c | 44 +++++++------------------------ 13 files changed, 153 insertions(+), 189 deletions(-) create mode 100644 src/sensor_bulk.c create mode 100644 src/sensor_bulk.h diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index 76fd4ca45f67..b91224d506c3 100644 --- a/klippy/extras/adxl345.py +++ b/klippy/extras/adxl345.py @@ -187,7 +187,7 @@ def read_axes_map(config): MIN_MSG_TIME = 0.100 BYTES_PER_SAMPLE = 5 -SAMPLES_PER_BLOCK = 10 +SAMPLES_PER_BLOCK = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE BATCH_UPDATES = 0.100 @@ -205,13 +205,12 @@ def __init__(self, config): self.mcu = mcu = self.spi.get_mcu() self.oid = oid = mcu.create_oid() self.query_adxl345_cmd = None - self.query_adxl345_status_cmd = None mcu.add_config_cmd("config_adxl345 oid=%d spi_oid=%d" % (oid, self.spi.get_oid())) mcu.add_config_cmd("query_adxl345 oid=%d clock=0 rest_ticks=0" % (oid,), on_restart=True) mcu.register_config_callback(self._build_config) - self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, "adxl345_data", oid) + self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=oid) # Clock tracking chip_smooth = self.data_rate * BATCH_UPDATES * 2 self.clock_sync = bulk_sensor.ClockSyncRegression(mcu, chip_smooth) @@ -230,10 +229,8 @@ def _build_config(self): cmdqueue = self.spi.get_command_queue() self.query_adxl345_cmd = self.mcu.lookup_command( "query_adxl345 oid=%c clock=%u rest_ticks=%u", cq=cmdqueue) - self.query_adxl345_status_cmd = self.mcu.lookup_query_command( - "query_adxl345_status oid=%c", - "adxl345_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" - " buffered=%c fifo=%c limit_count=%hu", oid=self.oid, cq=cmdqueue) + self.clock_updater.setup_query_command( + self.mcu, "query_adxl345_status oid=%c", oid=self.oid, cq=cmdqueue) def read_reg(self, reg): params = self.spi.spi_transfer([reg | REG_MOD_READ, 0x00]) response = bytearray(params['response']) @@ -286,17 +283,6 @@ def _extract_samples(self, raw_samples): self.clock_sync.set_last_chip_clock(seq * SAMPLES_PER_BLOCK + i) del samples[count:] return samples - def _update_clock(self, minclock=0): - # Query current state - for retry in range(5): - params = self.query_adxl345_status_cmd.send([self.oid], - minclock=minclock) - fifo = params['fifo'] & 0x7f - if fifo <= 32: - break - else: - raise self.printer.command_error("Unable to query adxl345 fifo") - self.clock_updater.update_clock(params) # Start, stop, and process message batches def _start_measurements(self): # In case of miswiring, testing ADXL345 device ID prevents treating @@ -325,8 +311,6 @@ def _start_measurements(self): logging.info("ADXL345 starting '%s' measurements", self.name) # Initialize clock tracking self.clock_updater.note_start(reqclock) - self._update_clock(minclock=reqclock) - self.clock_updater.clear_duration_filter() self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading @@ -334,7 +318,7 @@ def _finish_measurements(self): self.bulk_queue.clear_samples() logging.info("ADXL345 finished '%s' measurements", self.name) def _process_batch(self, eventtime): - self._update_clock() + self.clock_updater.update_clock() raw_samples = self.bulk_queue.pull_samples() if not raw_samples: return {} @@ -342,7 +326,7 @@ def _process_batch(self, eventtime): if not samples: return {} return {'data': samples, 'errors': self.last_error_count, - 'overflows': self.clock_updater.get_last_limit_count()} + 'overflows': self.clock_updater.get_last_overflows()} def load_config(config): return ADXL345(config) diff --git a/klippy/extras/angle.py b/klippy/extras/angle.py index 163168d0b6e8..23f402a7e0f8 100644 --- a/klippy/extras/angle.py +++ b/klippy/extras/angle.py @@ -412,7 +412,7 @@ def cmd_ANGLE_DEBUG_WRITE(self, gcmd): self._write_reg(reg, val) BYTES_PER_SAMPLE = 3 -SAMPLES_PER_BLOCK = 16 +SAMPLES_PER_BLOCK = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE SAMPLE_PERIOD = 0.000400 BATCH_UPDATES = 0.100 @@ -445,7 +445,7 @@ def __init__(self, config): "query_spi_angle oid=%d clock=0 rest_ticks=0 time_shift=0" % (oid,), on_restart=True) mcu.register_config_callback(self._build_config) - self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, "spi_angle_data", oid) + self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=oid) # Process messages in batches self.batch_bulk = bulk_sensor.BatchBulkHelper( self.printer, self._process_batch, diff --git a/klippy/extras/bulk_sensor.py b/klippy/extras/bulk_sensor.py index 8d0c05416560..df5a5da25790 100644 --- a/klippy/extras/bulk_sensor.py +++ b/klippy/extras/bulk_sensor.py @@ -114,7 +114,7 @@ def handle_batch(self, msg): # Helper class to store incoming messages in a queue class BulkDataQueue: - def __init__(self, mcu, msg_name, oid): + def __init__(self, mcu, msg_name="sensor_bulk_data", oid=None): # Measurement storage (accessed from background thread) self.lock = threading.Lock() self.raw_samples = [] @@ -206,31 +206,37 @@ def __init__(self, clock_sync, bytes_per_sample): self.clock_sync = clock_sync self.bytes_per_sample = bytes_per_sample self.samples_per_block = MAX_BULK_MSG_SIZE // bytes_per_sample - self.mcu = clock_sync.mcu self.last_sequence = self.max_query_duration = 0 - self.last_limit_count = 0 + self.last_overflows = 0 + self.mcu = self.oid = self.query_status_cmd = None + def setup_query_command(self, mcu, msgformat, oid, cq): + self.mcu = mcu + self.oid = oid + self.query_status_cmd = self.mcu.lookup_query_command( + msgformat, "sensor_bulk_status oid=%c clock=%u query_ticks=%u" + " next_sequence=%hu buffered=%u possible_overflows=%hu", + oid=oid, cq=cq) def get_last_sequence(self): return self.last_sequence - def get_last_limit_count(self): - return self.last_limit_count + def get_last_overflows(self): + return self.last_overflows def clear_duration_filter(self): self.max_query_duration = 1 << 31 def note_start(self, reqclock): self.last_sequence = 0 - self.last_limit_count = 0 + self.last_overflows = 0 self.clock_sync.reset(reqclock, 0) self.clear_duration_filter() - def update_clock(self, params): - # Handle a status response message of the form: - # adxl345_status oid=x clock=x query_ticks=x next_sequence=x - # buffered=x fifo=x limit_count=x - fifo = params['fifo'] + self.update_clock(minclock=reqclock) + self.clear_duration_filter() + def update_clock(self, minclock=0): + params = self.query_status_cmd.send([self.oid], minclock=minclock) mcu_clock = self.mcu.clock32_to_clock64(params['clock']) seq_diff = (params['next_sequence'] - self.last_sequence) & 0xffff self.last_sequence += seq_diff buffered = params['buffered'] - lc_diff = (params['limit_count'] - self.last_limit_count) & 0xffff - self.last_limit_count += lc_diff + po_diff = (params['possible_overflows'] - self.last_overflows) & 0xffff + self.last_overflows += po_diff duration = params['query_ticks'] if duration > self.max_query_duration: # Skip measurement as a high query time could skew clock tracking @@ -239,7 +245,7 @@ def update_clock(self, params): return self.max_query_duration = 2 * duration msg_count = (self.last_sequence * self.samples_per_block - + buffered // self.bytes_per_sample + fifo) + + buffered // self.bytes_per_sample) # The "chip clock" is the message counter plus .5 for average # inaccuracy of query responses and plus .5 for assumed offset # of hardware processing time. diff --git a/klippy/extras/lis2dw.py b/klippy/extras/lis2dw.py index a7fe54d743f7..74911e6fbf14 100644 --- a/klippy/extras/lis2dw.py +++ b/klippy/extras/lis2dw.py @@ -33,7 +33,7 @@ MIN_MSG_TIME = 0.100 BYTES_PER_SAMPLE = 6 -SAMPLES_PER_BLOCK = 8 +SAMPLES_PER_BLOCK = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE BATCH_UPDATES = 0.100 @@ -49,13 +49,12 @@ def __init__(self, config): self.mcu = mcu = self.spi.get_mcu() self.oid = oid = mcu.create_oid() self.query_lis2dw_cmd = None - self.query_lis2dw_status_cmd = None mcu.add_config_cmd("config_lis2dw oid=%d spi_oid=%d" % (oid, self.spi.get_oid())) mcu.add_config_cmd("query_lis2dw oid=%d clock=0 rest_ticks=0" % (oid,), on_restart=True) mcu.register_config_callback(self._build_config) - self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, "lis2dw_data", oid) + self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=oid) # Clock tracking chip_smooth = self.data_rate * BATCH_UPDATES * 2 self.clock_sync = bulk_sensor.ClockSyncRegression(mcu, chip_smooth) @@ -75,10 +74,8 @@ def _build_config(self): cmdqueue = self.spi.get_command_queue() self.query_lis2dw_cmd = self.mcu.lookup_command( "query_lis2dw oid=%c clock=%u rest_ticks=%u", cq=cmdqueue) - self.query_lis2dw_status_cmd = self.mcu.lookup_query_command( - "query_lis2dw_status oid=%c", - "lis2dw_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" - " buffered=%c fifo=%c limit_count=%hu", oid=self.oid, cq=cmdqueue) + self.clock_updater.setup_query_command( + self.mcu, "query_lis2dw_status oid=%c", oid=self.oid, cq=cmdqueue) def read_reg(self, reg): params = self.spi.spi_transfer([reg | REG_MOD_READ, 0x00]) response = bytearray(params['response']) @@ -133,10 +130,6 @@ def _extract_samples(self, raw_samples): self.clock_sync.set_last_chip_clock(seq * SAMPLES_PER_BLOCK + i) del samples[count:] return samples - def _update_clock(self, minclock=0): - params = self.query_lis2dw_status_cmd.send([self.oid], - minclock=minclock) - self.clock_updater.update_clock(params) # Start, stop, and process message batches def _start_measurements(self): # In case of miswiring, testing LIS2DW device ID prevents treating @@ -170,8 +163,6 @@ def _start_measurements(self): logging.info("LIS2DW starting '%s' measurements", self.name) # Initialize clock tracking self.clock_updater.note_start(reqclock) - self._update_clock(minclock=reqclock) - self.clock_updater.clear_duration_filter() self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading @@ -180,7 +171,7 @@ def _finish_measurements(self): logging.info("LIS2DW finished '%s' measurements", self.name) self.set_reg(REG_LIS2DW_FIFO_CTRL, 0x00) def _process_batch(self, eventtime): - self._update_clock() + self.clock_updater.update_clock() raw_samples = self.bulk_queue.pull_samples() if not raw_samples: return {} @@ -188,7 +179,7 @@ def _process_batch(self, eventtime): if not samples: return {} return {'data': samples, 'errors': self.last_error_count, - 'overflows': self.clock_updater.get_last_limit_count()} + 'overflows': self.clock_updater.get_last_overflows()} def load_config(config): return LIS2DW(config) diff --git a/klippy/extras/mpu9250.py b/klippy/extras/mpu9250.py index 41376dc35d56..4626e1c0c69b 100644 --- a/klippy/extras/mpu9250.py +++ b/klippy/extras/mpu9250.py @@ -50,7 +50,7 @@ MIN_MSG_TIME = 0.100 BYTES_PER_SAMPLE = 6 -SAMPLES_PER_BLOCK = 8 +SAMPLES_PER_BLOCK = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE BATCH_UPDATES = 0.100 @@ -70,9 +70,8 @@ def __init__(self, config): self.mcu = mcu = self.i2c.get_mcu() self.oid = oid = mcu.create_oid() self.query_mpu9250_cmd = None - self.query_mpu9250_status_cmd = None mcu.register_config_callback(self._build_config) - self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, "mpu9250_data", oid) + self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=oid) # Clock tracking chip_smooth = self.data_rate * BATCH_UPDATES * 2 self.clock_sync = bulk_sensor.ClockSyncRegression(mcu, chip_smooth) @@ -95,10 +94,8 @@ def _build_config(self): % (self.oid,), on_restart=True) self.query_mpu9250_cmd = self.mcu.lookup_command( "query_mpu9250 oid=%c clock=%u rest_ticks=%u", cq=cmdqueue) - self.query_mpu9250_status_cmd = self.mcu.lookup_query_command( - "query_mpu9250_status oid=%c", - "mpu9250_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" - " buffered=%c fifo=%u limit_count=%hu", oid=self.oid, cq=cmdqueue) + self.clock_updater.setup_query_command( + self.mcu, "query_mpu9250_status oid=%c", oid=self.oid, cq=cmdqueue) def read_reg(self, reg): params = self.i2c.i2c_read([reg], 1) return bytearray(params['response'])[0] @@ -142,11 +139,6 @@ def _extract_samples(self, raw_samples): self.clock_sync.set_last_chip_clock(seq * SAMPLES_PER_BLOCK + i) del samples[count:] return samples - - def _update_clock(self, minclock=0): - params = self.query_mpu9250_status_cmd.send([self.oid], - minclock=minclock) - self.clock_updater.update_clock(params) # Start, stop, and process message batches def _start_measurements(self): # In case of miswiring, testing MPU9250 device ID prevents treating @@ -184,8 +176,6 @@ def _start_measurements(self): logging.info("MPU9250 starting '%s' measurements", self.name) # Initialize clock tracking self.clock_updater.note_start(reqclock) - self._update_clock(minclock=reqclock) - self.clock_updater.clear_duration_filter() self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading @@ -195,7 +185,7 @@ def _finish_measurements(self): self.set_reg(REG_PWR_MGMT_1, SET_PWR_MGMT_1_SLEEP) self.set_reg(REG_PWR_MGMT_2, SET_PWR_MGMT_2_OFF) def _process_batch(self, eventtime): - self._update_clock() + self.clock_updater.update_clock() raw_samples = self.bulk_queue.pull_samples() if not raw_samples: return {} @@ -203,7 +193,7 @@ def _process_batch(self, eventtime): if not samples: return {} return {'data': samples, 'errors': self.last_error_count, - 'overflows': self.clock_updater.get_last_limit_count()} + 'overflows': self.clock_updater.get_last_overflows()} def load_config(config): return MPU9250(config) diff --git a/src/Kconfig b/src/Kconfig index aaf506539493..91376910c3cf 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -112,6 +112,10 @@ config WANT_SOFTWARE_SPI bool depends on HAVE_GPIO && HAVE_GPIO_SPI default y +config NEED_SENSOR_BULK + bool + depends on WANT_SENSORS || WANT_LIS2DW + default y menu "Optional features (to reduce code size)" depends on HAVE_LIMITED_CODE_SIZE config WANT_GPIO_BITBANGING diff --git a/src/Makefile b/src/Makefile index 8d771f9eb484..eddad9783d96 100644 --- a/src/Makefile +++ b/src/Makefile @@ -16,6 +16,7 @@ src-$(CONFIG_WANT_SOFTWARE_SPI) += spi_software.c src-$(CONFIG_WANT_SOFTWARE_I2C) += i2c_software.c sensors-src-$(CONFIG_HAVE_GPIO_SPI) := thermocouple.c sensor_adxl345.c \ sensor_angle.c -src-$(CONFIG_WANT_LIS2DW) += sensor_lis2dw.c sensors-src-$(CONFIG_HAVE_GPIO_I2C) += sensor_mpu9250.c src-$(CONFIG_WANT_SENSORS) += $(sensors-src-y) +src-$(CONFIG_WANT_LIS2DW) += sensor_lis2dw.c +src-$(CONFIG_NEED_SENSOR_BULK) += sensor_bulk.c diff --git a/src/sensor_adxl345.c b/src/sensor_adxl345.c index 5ec3945e82cf..8cd471ef5d80 100644 --- a/src/sensor_adxl345.c +++ b/src/sensor_adxl345.c @@ -10,15 +10,15 @@ #include "basecmd.h" // oid_alloc #include "command.h" // DECL_COMMAND #include "sched.h" // DECL_TASK +#include "sensor_bulk.h" // sensor_bulk_report #include "spicmds.h" // spidev_transfer struct adxl345 { struct timer timer; uint32_t rest_ticks; struct spidev_s *spi; - uint16_t sequence, limit_count; - uint8_t flags, data_count; - uint8_t data[50]; + uint8_t flags; + struct sensor_bulk sb; }; enum { @@ -47,27 +47,6 @@ command_config_adxl345(uint32_t *args) } DECL_COMMAND(command_config_adxl345, "config_adxl345 oid=%c spi_oid=%c"); -// Report local measurement buffer -static void -adxl_report(struct adxl345 *ax, uint8_t oid) -{ - sendf("adxl345_data oid=%c sequence=%hu data=%*s" - , oid, ax->sequence, ax->data_count, ax->data); - ax->data_count = 0; - ax->sequence++; -} - -// Report buffer and fifo status -static void -adxl_status(struct adxl345 *ax, uint_fast8_t oid - , uint32_t time1, uint32_t time2, uint_fast8_t fifo) -{ - sendf("adxl345_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" - " buffered=%c fifo=%c limit_count=%hu" - , oid, time1, time2-time1, ax->sequence - , ax->data_count, fifo, ax->limit_count); -} - // Helper code to reschedule the adxl345_event() timer static void adxl_reschedule_timer(struct adxl345 *ax) @@ -87,6 +66,8 @@ adxl_reschedule_timer(struct adxl345 *ax) #define SET_FIFO_CTL 0x90 +#define BYTES_PER_SAMPLE 5 + // Query accelerometer data static void adxl_query(struct adxl345 *ax, uint8_t oid) @@ -96,7 +77,7 @@ adxl_query(struct adxl345 *ax, uint8_t oid) spidev_transfer(ax->spi, 1, sizeof(msg), msg); // Extract x, y, z measurements uint_fast8_t fifo_status = msg[8] & ~0x80; // Ignore trigger bit - uint8_t *d = &ax->data[ax->data_count]; + uint8_t *d = &ax->sb.data[ax->sb.data_count]; if (((msg[2] & 0xf0) && (msg[2] & 0xf0) != 0xf0) || ((msg[4] & 0xf0) && (msg[4] & 0xf0) != 0xf0) || ((msg[6] & 0xf0) && (msg[6] & 0xf0) != 0xf0) @@ -112,12 +93,12 @@ adxl_query(struct adxl345 *ax, uint8_t oid) d[3] = (msg[2] & 0x1f) | (msg[6] << 5); // x high bits and z high bits d[4] = (msg[4] & 0x1f) | ((msg[6] << 2) & 0x60); // y high and z high } - ax->data_count += 5; - if (ax->data_count + 5 > ARRAY_SIZE(ax->data)) - adxl_report(ax, oid); + ax->sb.data_count += BYTES_PER_SAMPLE; + if (ax->sb.data_count + BYTES_PER_SAMPLE > ARRAY_SIZE(ax->sb.data)) + sensor_bulk_report(&ax->sb, oid); // Check fifo status if (fifo_status >= 31) - ax->limit_count++; + ax->sb.possible_overflows++; if (fifo_status > 1 && fifo_status <= 32) { // More data in fifo - wake this task again sched_wake_task(&adxl345_wake); @@ -166,8 +147,7 @@ command_query_adxl345(uint32_t *args) ax->timer.waketime = args[1]; ax->rest_ticks = args[2]; ax->flags = AX_HAVE_START; - ax->sequence = ax->limit_count = 0; - ax->data_count = 0; + sensor_bulk_reset(&ax->sb); sched_add_timer(&ax->timer); } DECL_COMMAND(command_query_adxl345, @@ -178,10 +158,17 @@ command_query_adxl345_status(uint32_t *args) { struct adxl345 *ax = oid_lookup(args[0], command_config_adxl345); uint8_t msg[2] = { AR_FIFO_STATUS | AM_READ, 0x00 }; + uint32_t time1 = timer_read_time(); spidev_transfer(ax->spi, 1, sizeof(msg), msg); uint32_t time2 = timer_read_time(); - adxl_status(ax, args[0], time1, time2, msg[1]); + + uint_fast8_t fifo_status = msg[1] & ~0x80; // Ignore trigger bit + if (fifo_status > 32) + // Query error - don't send response - host will retry + return; + sensor_bulk_status(&ax->sb, args[0], time1, time2-time1 + , fifo_status * BYTES_PER_SAMPLE); } DECL_COMMAND(command_query_adxl345_status, "query_adxl345_status oid=%c"); diff --git a/src/sensor_angle.c b/src/sensor_angle.c index 865670b8994c..54caecc219e7 100644 --- a/src/sensor_angle.c +++ b/src/sensor_angle.c @@ -10,6 +10,7 @@ #include "board/irq.h" // irq_disable #include "command.h" // DECL_COMMAND #include "sched.h" // DECL_TASK +#include "sensor_bulk.h" // sensor_bulk_report #include "spicmds.h" // spidev_transfer enum { SA_CHIP_A1333, SA_CHIP_AS5047D, SA_CHIP_TLE5012B, SA_CHIP_MAX }; @@ -29,15 +30,16 @@ struct spi_angle { struct timer timer; uint32_t rest_ticks; struct spidev_s *spi; - uint16_t sequence; - uint8_t flags, chip_type, data_count, time_shift, overflow; - uint8_t data[48]; + uint8_t flags, chip_type, time_shift, overflow; + struct sensor_bulk sb; }; enum { SA_PENDING = 1<<2, }; +#define BYTES_PER_SAMPLE 3 + static struct task_wake angle_wake; // Event handler that wakes spi_angle_task() periodically @@ -72,32 +74,22 @@ command_config_spi_angle(uint32_t *args) DECL_COMMAND(command_config_spi_angle, "config_spi_angle oid=%c spi_oid=%c spi_angle_type=%c"); -// Report local measurement buffer -static void -angle_report(struct spi_angle *sa, uint8_t oid) -{ - sendf("spi_angle_data oid=%c sequence=%hu data=%*s" - , oid, sa->sequence, sa->data_count, sa->data); - sa->data_count = 0; - sa->sequence++; -} - // Send spi_angle_data message if buffer is full static void angle_check_report(struct spi_angle *sa, uint8_t oid) { - if (sa->data_count + 3 > ARRAY_SIZE(sa->data)) - angle_report(sa, oid); + if (sa->sb.data_count + BYTES_PER_SAMPLE > ARRAY_SIZE(sa->sb.data)) + sensor_bulk_report(&sa->sb, oid); } // Add an entry to the measurement buffer static void angle_add(struct spi_angle *sa, uint_fast8_t tcode, uint_fast16_t data) { - sa->data[sa->data_count] = tcode; - sa->data[sa->data_count + 1] = data; - sa->data[sa->data_count + 2] = data >> 8; - sa->data_count += 3; + sa->sb.data[sa->sb.data_count] = tcode; + sa->sb.data[sa->sb.data_count + 1] = data; + sa->sb.data[sa->sb.data_count + 2] = data >> 8; + sa->sb.data_count += BYTES_PER_SAMPLE; } // Add an error indicator to the measurement buffer @@ -237,8 +229,7 @@ command_query_spi_angle(uint32_t *args) // Start new measurements query sa->timer.waketime = args[1]; sa->rest_ticks = args[2]; - sa->sequence = 0; - sa->data_count = 0; + sensor_bulk_reset(&sa->sb); sa->time_shift = args[3]; sched_add_timer(&sa->timer); } diff --git a/src/sensor_bulk.c b/src/sensor_bulk.c new file mode 100644 index 000000000000..9b5c782c5c93 --- /dev/null +++ b/src/sensor_bulk.c @@ -0,0 +1,38 @@ +// Helper code for collecting and sending bulk sensor measurements +// +// Copyright (C) 2020-2023 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "command.h" // sendf +#include "sensor_bulk.h" // sensor_bulk_report + +// Reset counters +void +sensor_bulk_reset(struct sensor_bulk *sb) +{ + sb->sequence = 0; + sb->possible_overflows = 0; + sb->data_count = 0; +} + +// Report local measurement buffer +void +sensor_bulk_report(struct sensor_bulk *sb, uint8_t oid) +{ + sendf("sensor_bulk_data oid=%c sequence=%hu data=%*s" + , oid, sb->sequence, sb->data_count, sb->data); + sb->data_count = 0; + sb->sequence++; +} + +// Report buffer and fifo status +void +sensor_bulk_status(struct sensor_bulk *sb, uint8_t oid + , uint32_t time1, uint32_t query_ticks, uint32_t fifo) +{ + sendf("sensor_bulk_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" + " buffered=%u possible_overflows=%hu" + , oid, time1, query_ticks, sb->sequence + , sb->data_count + fifo, sb->possible_overflows); +} diff --git a/src/sensor_bulk.h b/src/sensor_bulk.h new file mode 100644 index 000000000000..9c130bea3c64 --- /dev/null +++ b/src/sensor_bulk.h @@ -0,0 +1,15 @@ +#ifndef __SENSOR_BULK_H +#define __SENSOR_BULK_H + +struct sensor_bulk { + uint16_t sequence, possible_overflows; + uint8_t data_count; + uint8_t data[52]; +}; + +void sensor_bulk_reset(struct sensor_bulk *sb); +void sensor_bulk_report(struct sensor_bulk *sb, uint8_t oid); +void sensor_bulk_status(struct sensor_bulk *sb, uint8_t oid + , uint32_t time1, uint32_t query_ticks, uint32_t fifo); + +#endif // sensor_bulk.h diff --git a/src/sensor_lis2dw.c b/src/sensor_lis2dw.c index 06dd32068b34..579ee1f716fb 100644 --- a/src/sensor_lis2dw.c +++ b/src/sensor_lis2dw.c @@ -11,6 +11,7 @@ #include "basecmd.h" // oid_alloc #include "command.h" // DECL_COMMAND #include "sched.h" // DECL_TASK +#include "sensor_bulk.h" // sensor_bulk_report #include "spicmds.h" // spidev_transfer #define LIS_AR_DATAX0 0x28 @@ -18,13 +19,14 @@ #define LIS_FIFO_CTRL 0x2E #define LIS_FIFO_SAMPLES 0x2F +#define BYTES_PER_SAMPLE 6 + struct lis2dw { struct timer timer; uint32_t rest_ticks; struct spidev_s *spi; - uint16_t sequence, limit_count; - uint8_t flags, data_count; - uint8_t data[48]; + uint8_t flags; + struct sensor_bulk sb; }; enum { @@ -53,27 +55,6 @@ command_config_lis2dw(uint32_t *args) } DECL_COMMAND(command_config_lis2dw, "config_lis2dw oid=%c spi_oid=%c"); -// Report local measurement buffer -static void -lis2dw_report(struct lis2dw *ax, uint8_t oid) -{ - sendf("lis2dw_data oid=%c sequence=%hu data=%*s" - , oid, ax->sequence, ax->data_count, ax->data); - ax->data_count = 0; - ax->sequence++; -} - -// Report buffer and fifo status -static void -lis2dw_status(struct lis2dw *ax, uint_fast8_t oid - , uint32_t time1, uint32_t time2, uint_fast8_t fifo) -{ - sendf("lis2dw_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" - " buffered=%c fifo=%c limit_count=%hu" - , oid, time1, time2-time1, ax->sequence - , ax->data_count, fifo, ax->limit_count); -} - // Helper code to reschedule the lis2dw_event() timer static void lis2dw_reschedule_timer(struct lis2dw *ax) @@ -93,7 +74,7 @@ lis2dw_query(struct lis2dw *ax, uint8_t oid) uint8_t fifo_empty,fifo_ovrn = 0; msg[0] = LIS_AR_DATAX0 | LIS_AM_READ ; - uint8_t *d = &ax->data[ax->data_count]; + uint8_t *d = &ax->sb.data[ax->sb.data_count]; spidev_transfer(ax->spi, 1, sizeof(msg), msg); @@ -108,13 +89,13 @@ lis2dw_query(struct lis2dw *ax, uint8_t oid) d[4] = msg[5]; // z low bits d[5] = msg[6]; // z high bits - ax->data_count += 6; - if (ax->data_count + 6 > ARRAY_SIZE(ax->data)) - lis2dw_report(ax, oid); + ax->sb.data_count += BYTES_PER_SAMPLE; + if (ax->sb.data_count + BYTES_PER_SAMPLE > ARRAY_SIZE(ax->sb.data)) + sensor_bulk_report(&ax->sb, oid); // Check fifo status if (fifo_ovrn) - ax->limit_count++; + ax->sb.possible_overflows++; // check if we need to run the task again (more packets in fifo?) if (!fifo_empty) { @@ -165,8 +146,7 @@ command_query_lis2dw(uint32_t *args) ax->timer.waketime = args[1]; ax->rest_ticks = args[2]; ax->flags = LIS_HAVE_START; - ax->sequence = ax->limit_count = 0; - ax->data_count = 0; + sensor_bulk_reset(&ax->sb); sched_add_timer(&ax->timer); } DECL_COMMAND(command_query_lis2dw, @@ -180,7 +160,8 @@ command_query_lis2dw_status(uint32_t *args) uint32_t time1 = timer_read_time(); spidev_transfer(ax->spi, 1, sizeof(msg), msg); uint32_t time2 = timer_read_time(); - lis2dw_status(ax, args[0], time1, time2, msg[1]&0x1f); + sensor_bulk_status(&ax->sb, args[0], time1, time2-time1 + , (msg[1] & 0x1f) * BYTES_PER_SAMPLE); } DECL_COMMAND(command_query_lis2dw_status, "query_lis2dw_status oid=%c"); diff --git a/src/sensor_mpu9250.c b/src/sensor_mpu9250.c index 7792b4d802b0..d52de811dfa5 100644 --- a/src/sensor_mpu9250.c +++ b/src/sensor_mpu9250.c @@ -12,6 +12,7 @@ #include "basecmd.h" // oid_alloc #include "command.h" // DECL_COMMAND #include "sched.h" // DECL_TASK +#include "sensor_bulk.h" // sensor_bulk_report #include "board/gpio.h" // i2c_read #include "i2ccmds.h" // i2cdev_oid_lookup @@ -46,11 +47,9 @@ struct mpu9250 { struct timer timer; uint32_t rest_ticks; struct i2cdev_s *i2c; - uint16_t sequence, limit_count, fifo_max, fifo_pkts_bytes; - uint8_t flags, data_count; - // msg size must be <= 255 due to Klipper api - // = SAMPLES_PER_BLOCK (from mpu9250.py) * BYTES_PER_FIFO_ENTRY + 1 - uint8_t data[BYTES_PER_BLOCK]; + uint16_t fifo_max, fifo_pkts_bytes; + uint8_t flags; + struct sensor_bulk sb; }; enum { @@ -92,27 +91,6 @@ command_config_mpu9250(uint32_t *args) } DECL_COMMAND(command_config_mpu9250, "config_mpu9250 oid=%c i2c_oid=%c"); -// Report local measurement buffer -static void -mp9250_report(struct mpu9250 *mp, uint8_t oid) -{ - sendf("mpu9250_data oid=%c sequence=%hu data=%*s" - , oid, mp->sequence, mp->data_count, mp->data); - mp->data_count = 0; - mp->sequence++; -} - -// Report buffer and fifo status -static void -mp9250_status(struct mpu9250 *mp, uint_fast8_t oid - , uint32_t time1, uint32_t time2, uint16_t fifo) -{ - sendf("mpu9250_status oid=%c clock=%u query_ticks=%u next_sequence=%hu" - " buffered=%c fifo=%u limit_count=%hu" - , oid, time1, time2-time1, mp->sequence - , mp->data_count, fifo, mp->limit_count); -} - // Helper code to reschedule the mpu9250_event() timer static void mp9250_reschedule_timer(struct mpu9250 *mp) @@ -135,10 +113,10 @@ mp9250_query(struct mpu9250 *mp, uint8_t oid) if (mp->fifo_pkts_bytes >= BYTES_PER_BLOCK) { uint8_t reg = AR_FIFO; i2c_read(mp->i2c->i2c_config, sizeof(reg), ® - , BYTES_PER_BLOCK, &mp->data[0]); - mp->data_count = BYTES_PER_BLOCK; + , BYTES_PER_BLOCK, &mp->sb.data[0]); + mp->sb.data_count = BYTES_PER_BLOCK; mp->fifo_pkts_bytes -= BYTES_PER_BLOCK; - mp9250_report(mp, oid); + sensor_bulk_report(&mp->sb, oid); } // If we have enough bytes remaining to fill another report wake again @@ -214,9 +192,7 @@ command_query_mpu9250(uint32_t *args) mp->timer.waketime = args[1]; mp->rest_ticks = args[2]; mp->flags = AX_HAVE_START; - mp->sequence = 0; - mp->limit_count = 0; - mp->data_count = 0; + sensor_bulk_reset(&mp->sb); mp->fifo_max = 0; mp->fifo_pkts_bytes = 0; sched_add_timer(&mp->timer); @@ -235,7 +211,7 @@ command_query_mpu9250_status(uint32_t *args) i2c_read(mp->i2c->i2c_config, sizeof(int_reg), int_reg, sizeof(int_msg), &int_msg); if (int_msg & FIFO_OVERFLOW_INT) - mp->limit_count++; + mp->sb.possible_overflows++; // Read latest FIFO count (with precise timing) uint8_t reg[] = {AR_FIFO_COUNT_H}; @@ -246,7 +222,7 @@ command_query_mpu9250_status(uint32_t *args) uint16_t fifo_bytes = ((msg[0] & 0x1f) << 8) | msg[1]; // Report status - mp9250_status(mp, args[0], time1, time2, fifo_bytes / BYTES_PER_FIFO_ENTRY); + sensor_bulk_status(&mp->sb, args[0], time1, time2-time1, fifo_bytes); } DECL_COMMAND(command_query_mpu9250_status, "query_mpu9250_status oid=%c"); From 2dc4cfc5df3bd2b3e040a73b86d59c7a3883fc7f Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 27 Dec 2023 12:58:53 -0500 Subject: [PATCH 014/190] bulk_sensor: Don't assume chip_clock is zero on start of queries Send an explicit clock query in ChipClockUpdater to seed the initial clock. Signed-off-by: Kevin O'Connor --- klippy/extras/adxl345.py | 2 +- klippy/extras/bulk_sensor.py | 16 ++++++++++------ klippy/extras/lis2dw.py | 2 +- klippy/extras/mpu9250.py | 2 +- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index b91224d506c3..45d8369f0671 100644 --- a/klippy/extras/adxl345.py +++ b/klippy/extras/adxl345.py @@ -310,7 +310,7 @@ def _start_measurements(self): reqclock=reqclock) logging.info("ADXL345 starting '%s' measurements", self.name) # Initialize clock tracking - self.clock_updater.note_start(reqclock) + self.clock_updater.note_start() self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading diff --git a/klippy/extras/bulk_sensor.py b/klippy/extras/bulk_sensor.py index df5a5da25790..ad486bc4b178 100644 --- a/klippy/extras/bulk_sensor.py +++ b/klippy/extras/bulk_sensor.py @@ -222,15 +222,15 @@ def get_last_overflows(self): return self.last_overflows def clear_duration_filter(self): self.max_query_duration = 1 << 31 - def note_start(self, reqclock): + def note_start(self): self.last_sequence = 0 self.last_overflows = 0 - self.clock_sync.reset(reqclock, 0) + # Set initial clock self.clear_duration_filter() - self.update_clock(minclock=reqclock) + self.update_clock(is_reset=True) self.clear_duration_filter() - def update_clock(self, minclock=0): - params = self.query_status_cmd.send([self.oid], minclock=minclock) + def update_clock(self, is_reset=False): + params = self.query_status_cmd.send([self.oid]) mcu_clock = self.mcu.clock32_to_clock64(params['clock']) seq_diff = (params['next_sequence'] - self.last_sequence) & 0xffff self.last_sequence += seq_diff @@ -250,4 +250,8 @@ def update_clock(self, minclock=0): # inaccuracy of query responses and plus .5 for assumed offset # of hardware processing time. chip_clock = msg_count + 1 - self.clock_sync.update(mcu_clock + duration // 2, chip_clock) + avg_mcu_clock = mcu_clock + duration // 2 + if is_reset: + self.clock_sync.reset(avg_mcu_clock, chip_clock) + else: + self.clock_sync.update(avg_mcu_clock, chip_clock) diff --git a/klippy/extras/lis2dw.py b/klippy/extras/lis2dw.py index 74911e6fbf14..469a4f0d6e8c 100644 --- a/klippy/extras/lis2dw.py +++ b/klippy/extras/lis2dw.py @@ -162,7 +162,7 @@ def _start_measurements(self): reqclock=reqclock) logging.info("LIS2DW starting '%s' measurements", self.name) # Initialize clock tracking - self.clock_updater.note_start(reqclock) + self.clock_updater.note_start() self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading diff --git a/klippy/extras/mpu9250.py b/klippy/extras/mpu9250.py index 4626e1c0c69b..421274bad1e6 100644 --- a/klippy/extras/mpu9250.py +++ b/klippy/extras/mpu9250.py @@ -175,7 +175,7 @@ def _start_measurements(self): reqclock=reqclock) logging.info("MPU9250 starting '%s' measurements", self.name) # Initialize clock tracking - self.clock_updater.note_start(reqclock) + self.clock_updater.note_start() self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading From 6f0e91f69fa813fc19833ebeed9b1ccde0ba97ee Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 27 Dec 2023 13:14:53 -0500 Subject: [PATCH 015/190] sensor_adxl345: No need to schedule start of bulk reading It's simpler and faster to enable the adxl345 in the python code. Signed-off-by: Kevin O'Connor --- klippy/extras/adxl345.py | 16 +++++------- src/sensor_adxl345.c | 55 +++++++++------------------------------- 2 files changed, 18 insertions(+), 53 deletions(-) diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index 45d8369f0671..738903f38a5d 100644 --- a/klippy/extras/adxl345.py +++ b/klippy/extras/adxl345.py @@ -184,8 +184,6 @@ def read_axes_map(config): raise config.error("Invalid axes_map parameter") return [am[a.strip()] for a in axes_map] -MIN_MSG_TIME = 0.100 - BYTES_PER_SAMPLE = 5 SAMPLES_PER_BLOCK = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE @@ -207,7 +205,7 @@ def __init__(self, config): self.query_adxl345_cmd = None mcu.add_config_cmd("config_adxl345 oid=%d spi_oid=%d" % (oid, self.spi.get_oid())) - mcu.add_config_cmd("query_adxl345 oid=%d clock=0 rest_ticks=0" + mcu.add_config_cmd("query_adxl345 oid=%d rest_ticks=0" % (oid,), on_restart=True) mcu.register_config_callback(self._build_config) self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=oid) @@ -228,7 +226,7 @@ def __init__(self, config): def _build_config(self): cmdqueue = self.spi.get_command_queue() self.query_adxl345_cmd = self.mcu.lookup_command( - "query_adxl345 oid=%c clock=%u rest_ticks=%u", cq=cmdqueue) + "query_adxl345 oid=%c rest_ticks=%u", cq=cmdqueue) self.clock_updater.setup_query_command( self.mcu, "query_adxl345_status oid=%c", oid=self.oid, cq=cmdqueue) def read_reg(self, reg): @@ -302,19 +300,17 @@ def _start_measurements(self): self.set_reg(REG_FIFO_CTL, SET_FIFO_CTL) # Start bulk reading self.bulk_queue.clear_samples() - systime = self.printer.get_reactor().monotonic() - print_time = self.mcu.estimated_print_time(systime) + MIN_MSG_TIME - reqclock = self.mcu.print_time_to_clock(print_time) rest_ticks = self.mcu.seconds_to_clock(4. / self.data_rate) - self.query_adxl345_cmd.send([self.oid, reqclock, rest_ticks], - reqclock=reqclock) + self.query_adxl345_cmd.send([self.oid, rest_ticks]) + self.set_reg(REG_POWER_CTL, 0x08) logging.info("ADXL345 starting '%s' measurements", self.name) # Initialize clock tracking self.clock_updater.note_start() self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading - self.query_adxl345_cmd.send_wait_ack([self.oid, 0, 0]) + self.set_reg(REG_POWER_CTL, 0x00) + self.query_adxl345_cmd.send_wait_ack([self.oid, 0]) self.bulk_queue.clear_samples() logging.info("ADXL345 finished '%s' measurements", self.name) def _process_batch(self, eventtime): diff --git a/src/sensor_adxl345.c b/src/sensor_adxl345.c index 8cd471ef5d80..32ce4c65365e 100644 --- a/src/sensor_adxl345.c +++ b/src/sensor_adxl345.c @@ -1,6 +1,6 @@ // Support for gathering acceleration data from ADXL345 chip // -// Copyright (C) 2020 Kevin O'Connor +// Copyright (C) 2020-2023 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -22,7 +22,7 @@ struct adxl345 { }; enum { - AX_HAVE_START = 1<<0, AX_RUNNING = 1<<1, AX_PENDING = 1<<2, + AX_PENDING = 1<<0, }; static struct task_wake adxl345_wake; @@ -58,7 +58,6 @@ adxl_reschedule_timer(struct adxl345 *ax) } // Chip registers -#define AR_POWER_CTL 0x2D #define AR_DATAX0 0x32 #define AR_FIFO_STATUS 0x39 #define AM_READ 0x80 @@ -99,59 +98,33 @@ adxl_query(struct adxl345 *ax, uint8_t oid) // Check fifo status if (fifo_status >= 31) ax->sb.possible_overflows++; - if (fifo_status > 1 && fifo_status <= 32) { + if (fifo_status > 1) { // More data in fifo - wake this task again sched_wake_task(&adxl345_wake); - } else if (ax->flags & AX_RUNNING) { + } else { // Sleep until next check time - sched_del_timer(&ax->timer); ax->flags &= ~AX_PENDING; adxl_reschedule_timer(ax); } } -// Startup measurements -static void -adxl_start(struct adxl345 *ax, uint8_t oid) -{ - sched_del_timer(&ax->timer); - ax->flags = AX_RUNNING; - uint8_t msg[2] = { AR_POWER_CTL, 0x08 }; - spidev_transfer(ax->spi, 0, sizeof(msg), msg); - adxl_reschedule_timer(ax); -} - -// End measurements -static void -adxl_stop(struct adxl345 *ax, uint8_t oid) -{ - // Disable measurements - sched_del_timer(&ax->timer); - ax->flags = 0; - uint8_t msg[2] = { AR_POWER_CTL, 0x00 }; - spidev_transfer(ax->spi, 0, sizeof(msg), msg); -} - void command_query_adxl345(uint32_t *args) { struct adxl345 *ax = oid_lookup(args[0], command_config_adxl345); - if (!args[2]) { + sched_del_timer(&ax->timer); + ax->flags = 0; + if (!args[1]) // End measurements - adxl_stop(ax, args[0]); return; - } + // Start new measurements query - sched_del_timer(&ax->timer); - ax->timer.waketime = args[1]; - ax->rest_ticks = args[2]; - ax->flags = AX_HAVE_START; + ax->rest_ticks = args[1]; sensor_bulk_reset(&ax->sb); - sched_add_timer(&ax->timer); + adxl_reschedule_timer(ax); } -DECL_COMMAND(command_query_adxl345, - "query_adxl345 oid=%c clock=%u rest_ticks=%u"); +DECL_COMMAND(command_query_adxl345, "query_adxl345 oid=%c rest_ticks=%u"); void command_query_adxl345_status(uint32_t *args) @@ -181,11 +154,7 @@ adxl345_task(void) struct adxl345 *ax; foreach_oid(oid, ax, command_config_adxl345) { uint_fast8_t flags = ax->flags; - if (!(flags & AX_PENDING)) - continue; - if (flags & AX_HAVE_START) - adxl_start(ax, oid); - else + if (flags & AX_PENDING) adxl_query(ax, oid); } } From d853c1981107e6602c65285f91c805e8f33c3846 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 27 Dec 2023 12:02:10 -0500 Subject: [PATCH 016/190] sensor_mpu9250: No need to schedule start of bulk reading It's simpler and faster to enable the mpu9250 in the python code. Signed-off-by: Kevin O'Connor --- klippy/extras/mpu9250.py | 26 ++++---- src/sensor_mpu9250.c | 125 +++++++++------------------------------ 2 files changed, 43 insertions(+), 108 deletions(-) diff --git a/klippy/extras/mpu9250.py b/klippy/extras/mpu9250.py index 421274bad1e6..b4b8c4391674 100644 --- a/klippy/extras/mpu9250.py +++ b/klippy/extras/mpu9250.py @@ -30,6 +30,7 @@ REG_USER_CTRL = 0x6A REG_PWR_MGMT_1 = 0x6B REG_PWR_MGMT_2 = 0x6C +REG_INT_STATUS = 0x3A SAMPLE_RATE_DIVS = { 4000:0x00 } @@ -40,6 +41,10 @@ SET_PWR_MGMT_1_SLEEP= 0x40 SET_PWR_MGMT_2_ACCEL_ON = 0x07 SET_PWR_MGMT_2_OFF = 0x3F +SET_USER_FIFO_RESET = 0x04 +SET_USER_FIFO_EN = 0x40 +SET_ENABLE_FIFO = 0x08 +SET_DISABLE_FIFO = 0x00 FREEFALL_ACCEL = 9.80665 * 1000. # SCALE = 1/4096 g/LSB @8g scale * Earth gravity in mm/s**2 @@ -47,8 +52,6 @@ FIFO_SIZE = 512 -MIN_MSG_TIME = 0.100 - BYTES_PER_SAMPLE = 6 SAMPLES_PER_BLOCK = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE @@ -90,10 +93,10 @@ def _build_config(self): cmdqueue = self.i2c.get_command_queue() self.mcu.add_config_cmd("config_mpu9250 oid=%d i2c_oid=%d" % (self.oid, self.i2c.get_oid())) - self.mcu.add_config_cmd("query_mpu9250 oid=%d clock=0 rest_ticks=0" + self.mcu.add_config_cmd("query_mpu9250 oid=%d rest_ticks=0" % (self.oid,), on_restart=True) self.query_mpu9250_cmd = self.mcu.lookup_command( - "query_mpu9250 oid=%c clock=%u rest_ticks=%u", cq=cmdqueue) + "query_mpu9250 oid=%c rest_ticks=%u", cq=cmdqueue) self.clock_updater.setup_query_command( self.mcu, "query_mpu9250_status oid=%c", oid=self.oid, cq=cmdqueue) def read_reg(self, reg): @@ -164,22 +167,25 @@ def _start_measurements(self): self.set_reg(REG_CONFIG, SET_CONFIG) self.set_reg(REG_ACCEL_CONFIG, SET_ACCEL_CONFIG) self.set_reg(REG_ACCEL_CONFIG2, SET_ACCEL_CONFIG2) + # Reset fifo + self.set_reg(REG_FIFO_EN, SET_DISABLE_FIFO) + self.set_reg(REG_USER_CTRL, SET_USER_FIFO_RESET) + self.set_reg(REG_USER_CTRL, SET_USER_FIFO_EN) + self.read_reg(REG_INT_STATUS) # clear FIFO overflow flag # Start bulk reading self.bulk_queue.clear_samples() - systime = self.printer.get_reactor().monotonic() - print_time = self.mcu.estimated_print_time(systime) + MIN_MSG_TIME - reqclock = self.mcu.print_time_to_clock(print_time) rest_ticks = self.mcu.seconds_to_clock(4. / self.data_rate) - self.query_mpu9250_cmd.send([self.oid, reqclock, rest_ticks], - reqclock=reqclock) + self.query_mpu9250_cmd.send([self.oid, rest_ticks]) + self.set_reg(REG_FIFO_EN, SET_ENABLE_FIFO) logging.info("MPU9250 starting '%s' measurements", self.name) # Initialize clock tracking self.clock_updater.note_start() self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading - self.query_mpu9250_cmd.send_wait_ack([self.oid, 0, 0]) + self.set_reg(REG_FIFO_EN, SET_DISABLE_FIFO) + self.query_mpu9250_cmd.send_wait_ack([self.oid, 0]) self.bulk_queue.clear_samples() logging.info("MPU9250 finished '%s' measurements", self.name) self.set_reg(REG_PWR_MGMT_1, SET_PWR_MGMT_1_SLEEP) diff --git a/src/sensor_mpu9250.c b/src/sensor_mpu9250.c index d52de811dfa5..23c029211f72 100644 --- a/src/sensor_mpu9250.c +++ b/src/sensor_mpu9250.c @@ -2,7 +2,7 @@ // // Copyright (C) 2023 Matthew Swabey // Copyright (C) 2022 Harry Beyel -// Copyright (C) 2020-2021 Kevin O'Connor +// Copyright (C) 2020-2023 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -17,27 +17,10 @@ #include "i2ccmds.h" // i2cdev_oid_lookup // Chip registers -#define AR_FIFO_SIZE 512 - -#define AR_PWR_MGMT_1 0x6B -#define AR_PWR_MGMT_2 0x6C -#define AR_FIFO_EN 0x23 -#define AR_ACCEL_OUT_XH 0x3B -#define AR_USER_CTRL 0x6A #define AR_FIFO_COUNT_H 0x72 #define AR_FIFO 0x74 #define AR_INT_STATUS 0x3A -#define SET_ENABLE_FIFO 0x08 -#define SET_DISABLE_FIFO 0x00 -#define SET_USER_FIFO_RESET 0x04 -#define SET_USER_FIFO_EN 0x40 - -#define SET_PWR_SLEEP 0x40 -#define SET_PWR_WAKE 0x00 -#define SET_PWR_2_ACCEL 0x07 // only enable accelerometers -#define SET_PWR_2_NONE 0x3F // disable all sensors - #define FIFO_OVERFLOW_INT 0x10 #define BYTES_PER_FIFO_ENTRY 6 @@ -53,24 +36,11 @@ struct mpu9250 { }; enum { - AX_HAVE_START = 1<<0, AX_RUNNING = 1<<1, AX_PENDING = 1<<2, + AX_PENDING = 1<<0, }; static struct task_wake mpu9250_wake; -// Reads the fifo byte count from the device. -static uint16_t -get_fifo_status (struct mpu9250 *mp) -{ - uint8_t reg[] = {AR_FIFO_COUNT_H}; - uint8_t msg[2]; - i2c_read(mp->i2c->i2c_config, sizeof(reg), reg, sizeof(msg), msg); - uint16_t fifo_bytes = ((msg[0] & 0x1f) << 8) | msg[1]; - if (fifo_bytes > mp->fifo_max) - mp->fifo_max = fifo_bytes; - return fifo_bytes; -} - // Event handler that wakes mpu9250_task() periodically static uint_fast8_t mpu9250_event(struct timer *timer) @@ -101,6 +71,19 @@ mp9250_reschedule_timer(struct mpu9250 *mp) irq_enable(); } +// Reads the fifo byte count from the device. +static uint16_t +get_fifo_status(struct mpu9250 *mp) +{ + uint8_t reg[] = {AR_FIFO_COUNT_H}; + uint8_t msg[2]; + i2c_read(mp->i2c->i2c_config, sizeof(reg), reg, sizeof(msg), msg); + uint16_t fifo_bytes = ((msg[0] & 0x1f) << 8) | msg[1]; + if (fifo_bytes > mp->fifo_max) + mp->fifo_max = fifo_bytes; + return fifo_bytes; +} + // Query accelerometer data static void mp9250_query(struct mpu9250 *mp, uint8_t oid) @@ -123,82 +106,35 @@ mp9250_query(struct mpu9250 *mp, uint8_t oid) // otherwise schedule timed wakeup if (mp->fifo_pkts_bytes >= BYTES_PER_BLOCK) { sched_wake_task(&mpu9250_wake); - } else if (mp->flags & AX_RUNNING) { - sched_del_timer(&mp->timer); + } else { mp->flags &= ~AX_PENDING; mp9250_reschedule_timer(mp); } } -// Startup measurements -static void -mp9250_start(struct mpu9250 *mp, uint8_t oid) -{ - sched_del_timer(&mp->timer); - mp->flags = AX_RUNNING; - uint8_t msg[2]; - - msg[0] = AR_FIFO_EN; - msg[1] = SET_DISABLE_FIFO; // disable FIFO - i2c_write(mp->i2c->i2c_config, sizeof(msg), msg); - - msg[0] = AR_USER_CTRL; - msg[1] = SET_USER_FIFO_RESET; // reset FIFO buffer - i2c_write(mp->i2c->i2c_config, sizeof(msg), msg); - - msg[0] = AR_USER_CTRL; - msg[1] = SET_USER_FIFO_EN; // enable FIFO buffer access - i2c_write(mp->i2c->i2c_config, sizeof(msg), msg); - - uint8_t int_reg[] = {AR_INT_STATUS}; // clear FIFO overflow flag - i2c_read(mp->i2c->i2c_config, sizeof(int_reg), int_reg, 1, msg); - - msg[0] = AR_FIFO_EN; - msg[1] = SET_ENABLE_FIFO; // enable accel output to FIFO - i2c_write(mp->i2c->i2c_config, sizeof(msg), msg); - - mp9250_reschedule_timer(mp); -} - -// End measurements -static void -mp9250_stop(struct mpu9250 *mp, uint8_t oid) -{ - // Disable measurements - sched_del_timer(&mp->timer); - mp->flags = 0; - - // disable accel FIFO - uint8_t msg[2] = { AR_FIFO_EN, SET_DISABLE_FIFO }; - i2c_write(mp->i2c->i2c_config, sizeof(msg), msg); - - // Uncomment and rebuilt to check for FIFO overruns when tuning - //output("mpu9240 limit_count=%u fifo_max=%u", - // mp->limit_count, mp->fifo_max); -} - void command_query_mpu9250(uint32_t *args) { struct mpu9250 *mp = oid_lookup(args[0], command_config_mpu9250); - if (!args[2]) { + sched_del_timer(&mp->timer); + mp->flags = 0; + if (!args[1]) { // End measurements - mp9250_stop(mp, args[0]); + + // Uncomment and rebuilt to check for FIFO overruns when tuning + //output("mpu9240 fifo_max=%u", mp->fifo_max); return; } + // Start new measurements query - sched_del_timer(&mp->timer); - mp->timer.waketime = args[1]; - mp->rest_ticks = args[2]; - mp->flags = AX_HAVE_START; + mp->rest_ticks = args[1]; sensor_bulk_reset(&mp->sb); mp->fifo_max = 0; mp->fifo_pkts_bytes = 0; - sched_add_timer(&mp->timer); + mp9250_reschedule_timer(mp); } -DECL_COMMAND(command_query_mpu9250, - "query_mpu9250 oid=%c clock=%u rest_ticks=%u"); +DECL_COMMAND(command_query_mpu9250, "query_mpu9250 oid=%c rest_ticks=%u"); void command_query_mpu9250_status(uint32_t *args) @@ -235,15 +171,8 @@ mpu9250_task(void) struct mpu9250 *mp; foreach_oid(oid, mp, command_config_mpu9250) { uint_fast8_t flags = mp->flags; - if (!(flags & AX_PENDING)) { - continue; - } - if (flags & AX_HAVE_START) { - mp9250_start(mp, oid); - } - else { + if (flags & AX_PENDING) mp9250_query(mp, oid); - } } } DECL_TASK(mpu9250_task); From d785b396a72b57c073ce5bdf7a9d5e9fe39fc914 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 27 Dec 2023 13:36:21 -0500 Subject: [PATCH 017/190] sensor_lis2dw: No need to schedule start of bulk reading It's simpler and faster to enable the lis2dw in the python code. Signed-off-by: Kevin O'Connor --- klippy/extras/lis2dw.py | 18 ++++++-------- src/sensor_lis2dw.c | 53 +++++++++-------------------------------- 2 files changed, 18 insertions(+), 53 deletions(-) diff --git a/klippy/extras/lis2dw.py b/klippy/extras/lis2dw.py index 469a4f0d6e8c..e96313027561 100644 --- a/klippy/extras/lis2dw.py +++ b/klippy/extras/lis2dw.py @@ -1,7 +1,7 @@ # Support for reading acceleration data from an LIS2DW chip # # Copyright (C) 2023 Zhou.XianMing -# Copyright (C) 2020-2021 Kevin O'Connor +# Copyright (C) 2020-2023 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import logging @@ -30,8 +30,6 @@ FREEFALL_ACCEL = 9.80665 SCALE = FREEFALL_ACCEL * 1.952 / 4 -MIN_MSG_TIME = 0.100 - BYTES_PER_SAMPLE = 6 SAMPLES_PER_BLOCK = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE @@ -51,7 +49,7 @@ def __init__(self, config): self.query_lis2dw_cmd = None mcu.add_config_cmd("config_lis2dw oid=%d spi_oid=%d" % (oid, self.spi.get_oid())) - mcu.add_config_cmd("query_lis2dw oid=%d clock=0 rest_ticks=0" + mcu.add_config_cmd("query_lis2dw oid=%d rest_ticks=0" % (oid,), on_restart=True) mcu.register_config_callback(self._build_config) self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=oid) @@ -73,7 +71,7 @@ def __init__(self, config): def _build_config(self): cmdqueue = self.spi.get_command_queue() self.query_lis2dw_cmd = self.mcu.lookup_command( - "query_lis2dw oid=%c clock=%u rest_ticks=%u", cq=cmdqueue) + "query_lis2dw oid=%c rest_ticks=%u", cq=cmdqueue) self.clock_updater.setup_query_command( self.mcu, "query_lis2dw_status oid=%c", oid=self.oid, cq=cmdqueue) def read_reg(self, reg): @@ -154,19 +152,17 @@ def _start_measurements(self): # Start bulk reading self.bulk_queue.clear_samples() - systime = self.printer.get_reactor().monotonic() - print_time = self.mcu.estimated_print_time(systime) + MIN_MSG_TIME - reqclock = self.mcu.print_time_to_clock(print_time) rest_ticks = self.mcu.seconds_to_clock(4. / self.data_rate) - self.query_lis2dw_cmd.send([self.oid, reqclock, rest_ticks], - reqclock=reqclock) + self.query_lis2dw_cmd.send([self.oid, rest_ticks]) + self.set_reg(REG_LIS2DW_FIFO_CTRL, 0xC0) logging.info("LIS2DW starting '%s' measurements", self.name) # Initialize clock tracking self.clock_updater.note_start() self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading - self.query_lis2dw_cmd.send_wait_ack([self.oid, 0, 0]) + self.set_reg(REG_LIS2DW_FIFO_CTRL, 0x00) + self.query_lis2dw_cmd.send_wait_ack([self.oid, 0]) self.bulk_queue.clear_samples() logging.info("LIS2DW finished '%s' measurements", self.name) self.set_reg(REG_LIS2DW_FIFO_CTRL, 0x00) diff --git a/src/sensor_lis2dw.c b/src/sensor_lis2dw.c index 579ee1f716fb..83922003c31b 100644 --- a/src/sensor_lis2dw.c +++ b/src/sensor_lis2dw.c @@ -1,7 +1,7 @@ // Support for gathering acceleration data from LIS2DW chip // // Copyright (C) 2023 Zhou.XianMing -// Copyright (C) 2020 Kevin O'Connor +// Copyright (C) 2020-2023 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -16,7 +16,6 @@ #define LIS_AR_DATAX0 0x28 #define LIS_AM_READ 0x80 -#define LIS_FIFO_CTRL 0x2E #define LIS_FIFO_SAMPLES 0x2F #define BYTES_PER_SAMPLE 6 @@ -30,7 +29,7 @@ struct lis2dw { }; enum { - LIS_HAVE_START = 1<<0, LIS_RUNNING = 1<<1, LIS_PENDING = 1<<2, + LIS_PENDING = 1<<0, }; static struct task_wake lis2dw_wake; @@ -101,56 +100,30 @@ lis2dw_query(struct lis2dw *ax, uint8_t oid) if (!fifo_empty) { // More data in fifo - wake this task again sched_wake_task(&lis2dw_wake); - } else if (ax->flags & LIS_RUNNING) { + } else { // Sleep until next check time - sched_del_timer(&ax->timer); ax->flags &= ~LIS_PENDING; lis2dw_reschedule_timer(ax); } } -// Startup measurements -static void -lis2dw_start(struct lis2dw *ax, uint8_t oid) -{ - sched_del_timer(&ax->timer); - ax->flags = LIS_RUNNING; - uint8_t ctrl[2] = {LIS_FIFO_CTRL , 0xC0}; - spidev_transfer(ax->spi, 0, sizeof(ctrl), ctrl); - lis2dw_reschedule_timer(ax); -} - -// End measurements -static void -lis2dw_stop(struct lis2dw *ax, uint8_t oid) -{ - // Disable measurements - sched_del_timer(&ax->timer); - ax->flags = 0; - uint8_t ctrl[2] = {LIS_FIFO_CTRL , 0}; - spidev_transfer(ax->spi, 0, sizeof(ctrl), ctrl); -} - void command_query_lis2dw(uint32_t *args) { struct lis2dw *ax = oid_lookup(args[0], command_config_lis2dw); - if (!args[2]) { + sched_del_timer(&ax->timer); + ax->flags = 0; + if (!args[1]) // End measurements - lis2dw_stop(ax, args[0]); return; - } + // Start new measurements query - sched_del_timer(&ax->timer); - ax->timer.waketime = args[1]; - ax->rest_ticks = args[2]; - ax->flags = LIS_HAVE_START; + ax->rest_ticks = args[1]; sensor_bulk_reset(&ax->sb); - sched_add_timer(&ax->timer); + lis2dw_reschedule_timer(ax); } -DECL_COMMAND(command_query_lis2dw, - "query_lis2dw oid=%c clock=%u rest_ticks=%u"); +DECL_COMMAND(command_query_lis2dw, "query_lis2dw oid=%c rest_ticks=%u"); void command_query_lis2dw_status(uint32_t *args) @@ -174,11 +147,7 @@ lis2dw_task(void) struct lis2dw *ax; foreach_oid(oid, ax, command_config_lis2dw) { uint_fast8_t flags = ax->flags; - if (!(flags & LIS_PENDING)) - continue; - if (flags & LIS_HAVE_START) - lis2dw_start(ax, oid); - else + if (flags & LIS_PENDING) lis2dw_query(ax, oid); } } From daf875e6e4b8cb461a57623ecac37cf0f1f240e8 Mon Sep 17 00:00:00 2001 From: BIGTREETECH <38851044+bigtreetech@users.noreply.github.com> Date: Mon, 22 Jan 2024 09:23:12 +0800 Subject: [PATCH 018/190] stm32g0: Disable internal pull-down resistors on UCPDx CCx pins, because klipper never uses UCPD (#6462) Signed-off-by: Alan.Ma from BigTreeTech --- src/stm32/stm32g0.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/stm32/stm32g0.c b/src/stm32/stm32g0.c index 7408612ade2e..819a5edd448f 100644 --- a/src/stm32/stm32g0.c +++ b/src/stm32/stm32g0.c @@ -162,6 +162,8 @@ bootloader_request(void) void armcm_main(void) { + // Disable internal pull-down resistors on UCPDx CCx pins + SYSCFG->CFGR1 |= (SYSCFG_CFGR1_UCPD1_STROBE | SYSCFG_CFGR1_UCPD2_STROBE); SCB->VTOR = (uint32_t)VectorTable; // Reset clock registers (in case bootloader has changed them) From f653db9c88aea646f506591ad2ea9b78eede4aa8 Mon Sep 17 00:00:00 2001 From: Jakub Date: Tue, 23 Jan 2024 00:55:34 +0100 Subject: [PATCH 019/190] stm32: Add 36KiB bootloader offset option (#6449) - This offset is used by Anycubic Kobra 2 Neo bootloader Signed-off-by: Jakub Przystasz --- src/stm32/Kconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index c06bb6ffb0c2..f8523f4926a9 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -285,6 +285,8 @@ choice bool "34KiB bootloader (Chitu v6 Bootloader)" if MACH_STM32F103 config STM32_FLASH_START_20200 bool "128KiB bootloader with 512 byte offset (Prusa Buddy)" if MACH_STM32F4x5 + config STM32_FLASH_START_9000 + bool "36KiB bootloader (Anycubic Kobra 2 Neo)" if MACH_STM32F1 config STM32_FLASH_START_C000 bool "48KiB bootloader (MKS Robin Nano V3)" if MACH_STM32F4x5 config STM32_FLASH_START_10000 @@ -312,6 +314,7 @@ config FLASH_APPLICATION_ADDRESS default 0x8007000 if STM32_FLASH_START_7000 default 0x8008000 if STM32_FLASH_START_8000 default 0x8008800 if STM32_FLASH_START_8800 + default 0x8009000 if STM32_FLASH_START_9000 default 0x800C000 if STM32_FLASH_START_C000 default 0x8010000 if STM32_FLASH_START_10000 default 0x8020000 if STM32_FLASH_START_20000 From 2e8b54ae5f01e2c4897eec109cd8a0cb3c0e5c4a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 22 Jan 2024 18:58:41 -0500 Subject: [PATCH 020/190] stm32: Remove product names from bootloader choices menu Signed-off-by: Kevin O'Connor --- src/stm32/Kconfig | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index f8523f4926a9..2ae90bee824d 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -282,24 +282,24 @@ choice config STM32_FLASH_START_8000 bool "32KiB bootloader" if MACH_STM32F1 || MACH_STM32F2 || MACH_STM32F4 || MACH_STM32F7 config STM32_FLASH_START_8800 - bool "34KiB bootloader (Chitu v6 Bootloader)" if MACH_STM32F103 + bool "34KiB bootloader" if MACH_STM32F103 config STM32_FLASH_START_20200 - bool "128KiB bootloader with 512 byte offset (Prusa Buddy)" if MACH_STM32F4x5 + bool "128KiB bootloader with 512 byte offset" if MACH_STM32F4x5 config STM32_FLASH_START_9000 - bool "36KiB bootloader (Anycubic Kobra 2 Neo)" if MACH_STM32F1 + bool "36KiB bootloader" if MACH_STM32F1 config STM32_FLASH_START_C000 - bool "48KiB bootloader (MKS Robin Nano V3)" if MACH_STM32F4x5 + bool "48KiB bootloader" if MACH_STM32F4x5 config STM32_FLASH_START_10000 bool "64KiB bootloader" if MACH_STM32F103 || MACH_STM32F4 config STM32_FLASH_START_800 - bool "2KiB bootloader (HID Bootloader)" if MACH_STM32F103 + bool "2KiB bootloader" if MACH_STM32F103 config STM32_FLASH_START_1000 bool "4KiB bootloader" if MACH_STM32F1 || MACH_STM32F0 config STM32_FLASH_START_4000 - bool "16KiB bootloader (HID Bootloader)" if MACH_STM32F207 || MACH_STM32F401 || MACH_STM32F4x5 || MACH_STM32F103 || MACH_STM32F072 + bool "16KiB bootloader" if MACH_STM32F207 || MACH_STM32F401 || MACH_STM32F4x5 || MACH_STM32F103 || MACH_STM32F072 config STM32_FLASH_START_20000 - bool "128KiB bootloader (SKR SE BX v2.0)" if MACH_STM32H743 || MACH_STM32H723 || MACH_STM32F7 + bool "128KiB bootloader" if MACH_STM32H743 || MACH_STM32H723 || MACH_STM32F7 config STM32_FLASH_START_0000 bool "No bootloader" From 4115ea128af3308c1a5af224fce83b12c2e97e1a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 13 Jan 2023 10:02:47 -0500 Subject: [PATCH 021/190] output_pin: Deprecate static_value parameter Remove support for configuring "static" pins in output_pin module. A "static" pin only saves a few bytes of memory in the micro-controller. The savings does not justify the increased code complexity. Deprecate the static_value parameter to warn users. In the interim, a static_value parameter will set both value and shutdown_value parameters. Signed-off-by: Kevin O'Connor --- config/generic-mini-rambo.cfg | 6 +-- config/generic-ultimaker-ultimainboard-v2.cfg | 8 ++-- config/printer-adimlab-2018.cfg | 6 +-- config/printer-creality-cr30-2021.cfg | 1 - config/printer-lulzbot-mini1-2016.cfg | 6 +-- config/printer-wanhao-duplicator-6-2016.cfg | 6 +-- docs/Config_Changes.md | 4 ++ docs/Config_Reference.md | 7 +--- klippy/extras/output_pin.py | 39 ++++++++++--------- 9 files changed, 42 insertions(+), 41 deletions(-) diff --git a/config/generic-mini-rambo.cfg b/config/generic-mini-rambo.cfg index 61e2ac847d01..1a616cf80418 100644 --- a/config/generic-mini-rambo.cfg +++ b/config/generic-mini-rambo.cfg @@ -84,7 +84,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.3 +value: 1.3 [output_pin stepper_z_current] pin: PL4 @@ -92,7 +92,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.3 +value: 1.3 [output_pin stepper_e_current] pin: PL5 @@ -100,7 +100,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.25 +value: 1.25 [static_digital_output stepper_config] pins: diff --git a/config/generic-ultimaker-ultimainboard-v2.cfg b/config/generic-ultimaker-ultimainboard-v2.cfg index 9a4d4e6da09a..b1ce3fa55882 100644 --- a/config/generic-ultimaker-ultimainboard-v2.cfg +++ b/config/generic-ultimaker-ultimainboard-v2.cfg @@ -97,7 +97,7 @@ max_z_accel: 30 [output_pin case_light] pin: PH5 -static_value: 1.0 +value: 1.0 # Motor current settings. [output_pin stepper_xy_current] @@ -107,7 +107,7 @@ scale: 2.000 # Max power setting. cycle_time: .000030 hardware_pwm: True -static_value: 1.200 +value: 1.200 # Power adjustment setting. [output_pin stepper_z_current] @@ -116,7 +116,7 @@ pwm: True scale: 2.000 cycle_time: .000030 hardware_pwm: True -static_value: 1.200 +value: 1.200 [output_pin stepper_e_current] pin: PL3 @@ -124,4 +124,4 @@ pwm: True scale: 2.000 cycle_time: .000030 hardware_pwm: True -static_value: 1.250 +value: 1.250 diff --git a/config/printer-adimlab-2018.cfg b/config/printer-adimlab-2018.cfg index 2f02173dde1b..d810e9d7e6fd 100644 --- a/config/printer-adimlab-2018.cfg +++ b/config/printer-adimlab-2018.cfg @@ -89,7 +89,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.3 +value: 1.3 [output_pin stepper_z_current] pin: PL4 @@ -97,7 +97,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.3 +value: 1.3 [output_pin stepper_e_current] pin: PL3 @@ -105,7 +105,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.25 +value: 1.25 [display] lcd_type: st7920 diff --git a/config/printer-creality-cr30-2021.cfg b/config/printer-creality-cr30-2021.cfg index de946920032b..1edc75313660 100644 --- a/config/printer-creality-cr30-2021.cfg +++ b/config/printer-creality-cr30-2021.cfg @@ -98,7 +98,6 @@ max_temp: 100 [output_pin led] pin: PC14 -static_value: 0 # Neopixel LED support # [neopixel led_neopixel] diff --git a/config/printer-lulzbot-mini1-2016.cfg b/config/printer-lulzbot-mini1-2016.cfg index 9be60cbddaaf..52b8061eeb4a 100644 --- a/config/printer-lulzbot-mini1-2016.cfg +++ b/config/printer-lulzbot-mini1-2016.cfg @@ -125,7 +125,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.300 +value: 1.300 [output_pin stepper_z_current] pin: PL4 @@ -133,7 +133,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.630 +value: 1.630 [output_pin stepper_e_current] pin: PL5 @@ -141,7 +141,7 @@ pwm: True scale: 2.0 cycle_time: .000030 hardware_pwm: True -static_value: 1.250 +value: 1.250 [static_digital_output stepper_config] # Microstepping pins diff --git a/config/printer-wanhao-duplicator-6-2016.cfg b/config/printer-wanhao-duplicator-6-2016.cfg index b1d35faecb82..de8a3de87d2a 100644 --- a/config/printer-wanhao-duplicator-6-2016.cfg +++ b/config/printer-wanhao-duplicator-6-2016.cfg @@ -86,7 +86,7 @@ pwm: True scale: 2.782 cycle_time: .000030 hardware_pwm: True -static_value: 1.2 +value: 1.2 [output_pin stepper_z_current] pin: PL4 @@ -94,7 +94,7 @@ pwm: True scale: 2.782 cycle_time: .000030 hardware_pwm: True -static_value: 1.2 +value: 1.2 [output_pin stepper_e_current] pin: PL3 @@ -102,7 +102,7 @@ pwm: True scale: 2.782 cycle_time: .000030 hardware_pwm: True -static_value: 1.0 +value: 1.0 [display] lcd_type: ssd1306 diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index 2ceb868db0b9..bd00e3d7eefd 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,10 @@ All dates in this document are approximate. ## Changes +20240123: The output_pin `static_value` parameter is deprecated. +Replace with `value` and `shutdown_value` parameters. The option will +be removed in the near future. + 20231216: The `[hall_filament_width_sensor]` is changed to trigger filament runout when the thickness of the filament exceeds `max_diameter`. The maximum diameter defaults to `default_nominal_filament_diameter + max_difference`. See diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 4b53ef264e44..a6964e5bdd81 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -3096,11 +3096,6 @@ pin: # If this is true, the value fields should be between 0 and 1; if it # is false the value fields should be either 0 or 1. The default is # False. -#static_value: -# If this is set, then the pin is assigned to this value at startup -# and the pin can not be changed during runtime. A static pin uses -# slightly less ram in the micro-controller. The default is to use -# runtime configuration of pins. #value: # The value to initially set the pin to during MCU configuration. # The default is 0 (for low voltage). @@ -3133,6 +3128,8 @@ pin: # then the 'value' parameter can be specified using the desired # amperage for the stepper. The default is to not scale the 'value' # parameter. +#static_value: +# This option is deprecated and should no longer be specified. ``` ### [pwm_tool] diff --git a/klippy/extras/output_pin.py b/klippy/extras/output_pin.py index 8b41aca7a454..7b78775be2a8 100644 --- a/klippy/extras/output_pin.py +++ b/klippy/extras/output_pin.py @@ -12,6 +12,7 @@ class PrinterOutputPin: def __init__(self, config): self.printer = config.get_printer() ppins = self.printer.lookup_object('pins') + # Determine pin type self.is_pwm = config.getboolean('pwm', False) if self.is_pwm: self.mcu_pin = ppins.setup_pin('pwm', config.get('pin')) @@ -26,34 +27,34 @@ def __init__(self, config): self.scale = 1. self.last_cycle_time = self.default_cycle_time = 0. self.last_print_time = 0. - static_value = config.getfloat('static_value', None, - minval=0., maxval=self.scale) + # Support mcu checking for maximum duration self.reactor = self.printer.get_reactor() self.resend_timer = None self.resend_interval = 0. + max_mcu_duration = config.getfloat('maximum_mcu_duration', 0., + minval=0.500, + maxval=MAX_SCHEDULE_TIME) + self.mcu_pin.setup_max_duration(max_mcu_duration) + if max_mcu_duration: + self.resend_interval = max_mcu_duration - RESEND_HOST_TIME + # Determine start and shutdown values + static_value = config.getfloat('static_value', None, + minval=0., maxval=self.scale) if static_value is not None: - self.mcu_pin.setup_max_duration(0.) - self.last_value = static_value / self.scale - self.mcu_pin.setup_start_value( - self.last_value, self.last_value, True) + config.deprecate('static_value') + self.last_value = self.shutdown_value = static_value / self.scale else: - max_mcu_duration = config.getfloat('maximum_mcu_duration', 0., - minval=0.500, - maxval=MAX_SCHEDULE_TIME) - self.mcu_pin.setup_max_duration(max_mcu_duration) - if max_mcu_duration: - self.resend_interval = max_mcu_duration - RESEND_HOST_TIME - self.last_value = config.getfloat( 'value', 0., minval=0., maxval=self.scale) / self.scale self.shutdown_value = config.getfloat( 'shutdown_value', 0., minval=0., maxval=self.scale) / self.scale - self.mcu_pin.setup_start_value(self.last_value, self.shutdown_value) - pin_name = config.get_name().split()[1] - gcode = self.printer.lookup_object('gcode') - gcode.register_mux_command("SET_PIN", "PIN", pin_name, - self.cmd_SET_PIN, - desc=self.cmd_SET_PIN_help) + self.mcu_pin.setup_start_value(self.last_value, self.shutdown_value) + # Register commands + pin_name = config.get_name().split()[1] + gcode = self.printer.lookup_object('gcode') + gcode.register_mux_command("SET_PIN", "PIN", pin_name, + self.cmd_SET_PIN, + desc=self.cmd_SET_PIN_help) def get_status(self, eventtime): return {'value': self.last_value} def _set_pin(self, print_time, value, cycle_time, is_resend=False): From 7abafb575ba7b098b9f1025a1d65717568a89876 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 13 Jan 2023 10:18:18 -0500 Subject: [PATCH 022/190] mcu: Remove support for "static" pins Update static_digital_output.py to directly configure static digital pins. There are no other users of "static" pins, so remove that support from mcu.py, replicape.py, and sx1509.py. This simplifies the low-level pin handling code. Signed-off-by: Kevin O'Connor --- klippy/extras/replicape.py | 13 +------------ klippy/extras/static_digital_output.py | 6 ++++-- klippy/extras/sx1509.py | 12 ++---------- klippy/mcu.py | 26 ++------------------------ 4 files changed, 9 insertions(+), 48 deletions(-) diff --git a/klippy/extras/replicape.py b/klippy/extras/replicape.py index 4c3762974654..d6737a35998c 100644 --- a/klippy/extras/replicape.py +++ b/klippy/extras/replicape.py @@ -30,7 +30,6 @@ def __init__(self, replicape, channel, pin_type, pin_params): self._invert = pin_params['invert'] self._start_value = self._shutdown_value = float(self._invert) self._is_enable = not not self._start_value - self._is_static = False self._last_clock = 0 self._pwm_max = 0. self._set_cmd = None @@ -44,28 +43,18 @@ def setup_cycle_time(self, cycle_time, hardware_pwm=False): if cycle_time != self._cycle_time: logging.info("Ignoring pca9685 cycle time of %.6f (using %.6f)", cycle_time, self._cycle_time) - def setup_start_value(self, start_value, shutdown_value, is_static=False): - if is_static and start_value != shutdown_value: - raise pins.error("Static pin can not have shutdown value") + def setup_start_value(self, start_value, shutdown_value): if self._invert: start_value = 1. - start_value shutdown_value = 1. - shutdown_value self._start_value = max(0., min(1., start_value)) self._shutdown_value = max(0., min(1., shutdown_value)) - self._is_static = is_static self._replicape.note_pwm_start_value( self._channel, self._start_value, self._shutdown_value) self._is_enable = not not self._start_value def _build_config(self): self._pwm_max = self._mcu.get_constant_float("PCA9685_MAX") cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time) - if self._is_static: - self._mcu.add_config_cmd( - "set_pca9685_out bus=%d addr=%d channel=%d" - " cycle_ticks=%d value=%d" % ( - self._bus, self._address, self._channel, - cycle_ticks, self._start_value * self._pwm_max)) - return self._mcu.request_move_queue_slot() self._oid = self._mcu.create_oid() self._mcu.add_config_cmd( diff --git a/klippy/extras/static_digital_output.py b/klippy/extras/static_digital_output.py index ce20937204c5..2fa0bb3f52c6 100644 --- a/klippy/extras/static_digital_output.py +++ b/klippy/extras/static_digital_output.py @@ -10,8 +10,10 @@ def __init__(self, config): ppins = printer.lookup_object('pins') pin_list = config.getlist('pins') for pin_desc in pin_list: - mcu_pin = ppins.setup_pin('digital_out', pin_desc) - mcu_pin.setup_start_value(1, 1, True) + pin_params = ppins.lookup_pin(pin_desc, can_invert=True) + mcu = pin_params['chip'] + mcu.add_config_cmd("set_digital_out pin=%s value=%d" + % (pin_params['pin'], not pin_params['invert'])) def load_config_prefix(config): return PrinterStaticDigitalOut(config) diff --git a/klippy/extras/sx1509.py b/klippy/extras/sx1509.py index 8b19dda801db..51080fe24d6e 100644 --- a/klippy/extras/sx1509.py +++ b/klippy/extras/sx1509.py @@ -104,7 +104,6 @@ def __init__(self, sx1509, pin_params): self._invert = pin_params['invert'] self._mcu.register_config_callback(self._build_config) self._start_value = self._shutdown_value = self._invert - self._is_static = False self._max_duration = 2. self._set_cmd = self._clear_cmd = None # Set direction to output @@ -116,12 +115,9 @@ def get_mcu(self): return self._mcu def setup_max_duration(self, max_duration): self._max_duration = max_duration - def setup_start_value(self, start_value, shutdown_value, is_static=False): - if is_static and start_value != shutdown_value: - raise pins.error("Static pin can not have shutdown value") + def setup_start_value(self, start_value, shutdown_value): self._start_value = (not not start_value) ^ self._invert self._shutdown_value = self._invert - self._is_static = is_static # We need to set the start value here so the register is # updated before the SX1509 class writes it. if self._start_value: @@ -148,7 +144,6 @@ def __init__(self, sx1509, pin_params): self._invert = pin_params['invert'] self._mcu.register_config_callback(self._build_config) self._start_value = self._shutdown_value = float(self._invert) - self._is_static = False self._max_duration = 2. self._hardware_pwm = False self._pwm_max = 0. @@ -182,15 +177,12 @@ def setup_max_duration(self, max_duration): def setup_cycle_time(self, cycle_time, hardware_pwm=False): self._cycle_time = cycle_time self._hardware_pwm = hardware_pwm - def setup_start_value(self, start_value, shutdown_value, is_static=False): - if is_static and start_value != shutdown_value: - raise pins.error("Static pin can not have shutdown value") + def setup_start_value(self, start_value, shutdown_value): if self._invert: start_value = 1. - start_value shutdown_value = 1. - shutdown_value self._start_value = max(0., min(1., start_value)) self._shutdown_value = max(0., min(1., shutdown_value)) - self._is_static = is_static def set_pwm(self, print_time, value, cycle_time=None): self._sx1509.set_register(self._i_on_reg, ~int(255 * value) if not self._invert diff --git a/klippy/mcu.py b/klippy/mcu.py index cfc389e76912..7f784de7f21c 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -335,7 +335,6 @@ def __init__(self, mcu, pin_params): self._pin = pin_params['pin'] self._invert = pin_params['invert'] self._start_value = self._shutdown_value = self._invert - self._is_static = False self._max_duration = 2. self._last_clock = 0 self._set_cmd = None @@ -343,17 +342,10 @@ def get_mcu(self): return self._mcu def setup_max_duration(self, max_duration): self._max_duration = max_duration - def setup_start_value(self, start_value, shutdown_value, is_static=False): - if is_static and start_value != shutdown_value: - raise pins.error("Static pin can not have shutdown value") + def setup_start_value(self, start_value, shutdown_value): self._start_value = (not not start_value) ^ self._invert self._shutdown_value = (not not shutdown_value) ^ self._invert - self._is_static = is_static def _build_config(self): - if self._is_static: - self._mcu.add_config_cmd("set_digital_out pin=%s value=%d" - % (self._pin, self._start_value)) - return if self._max_duration and self._start_value != self._shutdown_value: raise pins.error("Pin with max duration must have start" " value equal to shutdown value") @@ -389,7 +381,6 @@ def __init__(self, mcu, pin_params): self._pin = pin_params['pin'] self._invert = pin_params['invert'] self._start_value = self._shutdown_value = float(self._invert) - self._is_static = False self._last_clock = self._last_cycle_ticks = 0 self._pwm_max = 0. self._set_cmd = self._set_cycle_ticks = None @@ -400,15 +391,12 @@ def setup_max_duration(self, max_duration): def setup_cycle_time(self, cycle_time, hardware_pwm=False): self._cycle_time = cycle_time self._hardware_pwm = hardware_pwm - def setup_start_value(self, start_value, shutdown_value, is_static=False): - if is_static and start_value != shutdown_value: - raise pins.error("Static pin can not have shutdown value") + def setup_start_value(self, start_value, shutdown_value): if self._invert: start_value = 1. - start_value shutdown_value = 1. - shutdown_value self._start_value = max(0., min(1., start_value)) self._shutdown_value = max(0., min(1., shutdown_value)) - self._is_static = is_static def _build_config(self): if self._max_duration and self._start_value != self._shutdown_value: raise pins.error("Pin with max duration must have start" @@ -423,12 +411,6 @@ def _build_config(self): raise pins.error("PWM pin max duration too large") if self._hardware_pwm: self._pwm_max = self._mcu.get_constant_float("PWM_MAX") - if self._is_static: - self._mcu.add_config_cmd( - "set_pwm_out pin=%s cycle_ticks=%d value=%d" - % (self._pin, cycle_ticks, - self._start_value * self._pwm_max)) - return self._mcu.request_move_queue_slot() self._oid = self._mcu.create_oid() self._mcu.add_config_cmd( @@ -447,10 +429,6 @@ def _build_config(self): # Software PWM if self._shutdown_value not in [0., 1.]: raise pins.error("shutdown value must be 0.0 or 1.0 on soft pwm") - if self._is_static: - self._mcu.add_config_cmd("set_digital_out pin=%s value=%d" - % (self._pin, self._start_value >= 0.5)) - return if cycle_ticks >= 1<<31: raise pins.error("PWM pin cycle time too large") self._mcu.request_move_queue_slot() From 1baa45913ffd05a808d5d9ea0ae0161ebbaff247 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 12 Jan 2024 22:52:32 -0500 Subject: [PATCH 023/190] output_pin: Deprecate the maximum_mcu_duration parameter Advise users to configure a pwm_tool config section if checking for maximum mcu duration is required. Signed-off-by: Kevin O'Connor --- docs/Config_Changes.md | 4 ++++ docs/Config_Reference.md | 18 +++++++++--------- klippy/extras/output_pin.py | 1 + 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index bd00e3d7eefd..c660c42aa685 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,10 @@ All dates in this document are approximate. ## Changes +20240123: The output_pin `maximum_mcu_duration` parameter is +deprecated. Use a [pwm_tool config section](Config_Reference.md#pwm_tool) +instead. The option will be removed in the near future. + 20240123: The output_pin `static_value` parameter is deprecated. Replace with `value` and `shutdown_value` parameters. The option will be removed in the near future. diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index a6964e5bdd81..226b7a133864 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -3102,13 +3102,6 @@ pin: #shutdown_value: # The value to set the pin to on an MCU shutdown event. The default # is 0 (for low voltage). -#maximum_mcu_duration: -# The maximum duration a non-shutdown value may be driven by the MCU -# without an acknowledge from the host. -# If host can not keep up with an update, the MCU will shutdown -# and set all pins to their respective shutdown values. -# Default: 0 (disabled) -# Usual values are around 5 seconds. #cycle_time: 0.100 # The amount of time (in seconds) per PWM cycle. It is recommended # this be 10 milliseconds or greater when using software based PWM. @@ -3128,8 +3121,9 @@ pin: # then the 'value' parameter can be specified using the desired # amperage for the stepper. The default is to not scale the 'value' # parameter. +#maximum_mcu_duration: #static_value: -# This option is deprecated and should no longer be specified. +# These options are deprecated and should no longer be specified. ``` ### [pwm_tool] @@ -3144,9 +3138,15 @@ extended [g-code commands](G-Codes.md#output_pin). [pwm_tool my_tool] pin: # The pin to configure as an output. This parameter must be provided. +#maximum_mcu_duration: +# The maximum duration a non-shutdown value may be driven by the MCU +# without an acknowledge from the host. +# If host can not keep up with an update, the MCU will shutdown +# and set all pins to their respective shutdown values. +# Default: 0 (disabled) +# Usual values are around 5 seconds. #value: #shutdown_value: -#maximum_mcu_duration: #cycle_time: 0.100 #hardware_pwm: False #scale: diff --git a/klippy/extras/output_pin.py b/klippy/extras/output_pin.py index 7b78775be2a8..76789c37d839 100644 --- a/klippy/extras/output_pin.py +++ b/klippy/extras/output_pin.py @@ -36,6 +36,7 @@ def __init__(self, config): maxval=MAX_SCHEDULE_TIME) self.mcu_pin.setup_max_duration(max_mcu_duration) if max_mcu_duration: + config.deprecate('maximum_mcu_duration') self.resend_interval = max_mcu_duration - RESEND_HOST_TIME # Determine start and shutdown values static_value = config.getfloat('static_value', None, From fd2feff67df65c559cafc8fc5f2fd8601355e81a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 13 Jan 2023 11:20:19 -0500 Subject: [PATCH 024/190] pwm_cycle_time: New module for output pins with dynamic cycle times Remove support for changing the cycle time of pwm pins from the output_pin module. Use a new pwm_cycle_time module that supports setting dynamic cycle times. This simplifies the output_pin code and low-level pin update code. Signed-off-by: Kevin O'Connor --- config/sample-macros.cfg | 4 +- docs/Config_Changes.md | 5 ++ docs/Config_Reference.md | 18 +++++ docs/G-Codes.md | 30 +++++--- docs/Status_Reference.md | 7 ++ klippy/extras/output_pin.py | 25 +++---- klippy/extras/pwm_cycle_time.py | 123 ++++++++++++++++++++++++++++++++ test/klippy/pwm.cfg | 6 ++ test/klippy/pwm.test | 24 ++++--- 9 files changed, 204 insertions(+), 38 deletions(-) create mode 100644 klippy/extras/pwm_cycle_time.py diff --git a/config/sample-macros.cfg b/config/sample-macros.cfg index 5132e1c99c37..f5649d61a396 100644 --- a/config/sample-macros.cfg +++ b/config/sample-macros.cfg @@ -61,12 +61,10 @@ gcode: # P is the tone duration, S the tone frequency. # The frequency won't be pitch perfect. -[output_pin BEEPER_pin] +[pwm_cycle_time BEEPER_pin] pin: ar37 # Beeper pin. This parameter must be provided. # ar37 is the default RAMPS/MKS pin. -pwm: True -# A piezo beeper needs a PWM signal, a DC buzzer doesn't. value: 0 # Silent at power on, set to 1 if active low. shutdown_value: 0 diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index c660c42aa685..ae2c5f0a8ccd 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,11 @@ All dates in this document are approximate. ## Changes +20240123: The output_pin SET_PIN CYCLE_TIME parameter has been +removed. Use the new +[pwm_cycle_time](Config_Reference.md#pwm_cycle_time) module if it is +necessary to dynamically change a pwm pin's cycle time. + 20240123: The output_pin `maximum_mcu_duration` parameter is deprecated. Use a [pwm_tool config section](Config_Reference.md#pwm_tool) instead. The option will be removed in the near future. diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 226b7a133864..3b2f17709dd6 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -3153,6 +3153,24 @@ pin: # See the "output_pin" section for the definition of these parameters. ``` +### [pwm_cycle_time] + +Run-time configurable output pins with dynamic pwm cycle timing (one +may define any number of sections with an "pwm_cycle_time" prefix). +Pins configured here will be setup as output pins and one may modify +them at run-time using "SET_PIN PIN=my_pin VALUE=.1 CYCLE_TIME=0.100" +type extended [g-code commands](G-Codes.md#pwm_cycle_time). + +``` +[pwm_cycle_time my_pin] +pin: +#value: +#shutdown_value: +#cycle_time: 0.100 +#scale: +# See the "output_pin" section for information on these parameters. +``` + ### [static_digital_output] Statically configured digital output pins (one may define any number diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 8c70609f18c9..92cb76606afb 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -839,17 +839,10 @@ The following command is available when an enabled. #### SET_PIN -`SET_PIN PIN=config_name VALUE= [CYCLE_TIME=]`: Set -the pin to the given output `VALUE`. VALUE should be 0 or 1 for -"digital" output pins. For PWM pins, set to a value between 0.0 and -1.0, or between 0.0 and `scale` if a scale is configured in the -output_pin config section. - -Some pins (currently only "soft PWM" pins) support setting an explicit -cycle time using the CYCLE_TIME parameter (specified in seconds). Note -that the CYCLE_TIME parameter is not stored between SET_PIN commands -(any SET_PIN command without an explicit CYCLE_TIME parameter will use -the `cycle_time` specified in the output_pin config section). +`SET_PIN PIN=config_name VALUE=`: Set the pin to the given +output `VALUE`. VALUE should be 0 or 1 for "digital" output pins. For +PWM pins, set to a value between 0.0 and 1.0, or between 0.0 and +`scale` if a scale is configured in the output_pin config section. ### [palette2] @@ -978,6 +971,21 @@ babystepping), and subtract if from the probe's z_offset. This acts to take a frequently used babystepping value, and "make it permanent". Requires a `SAVE_CONFIG` to take effect. +### [pwm_cycle_time] + +The following command is available when a +[pwm_cycle_time config section](Config_Reference.md#pwm_cycle_time) +is enabled. + +#### SET_PIN +`SET_PIN PIN=config_name VALUE= [CYCLE_TIME=]`: +This command works similarly to [output_pin](#output_pin) SET_PIN +commands. The command here supports setting an explicit cycle time +using the CYCLE_TIME parameter (specified in seconds). Note that the +CYCLE_TIME parameter is not stored between SET_PIN commands (any +SET_PIN command without an explicit CYCLE_TIME parameter will use the +`cycle_time` specified in the pwm_cycle_time config section). + ### [query_adc] The query_adc module is automatically loaded. diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index b64108ae25c0..055d1dc0eac0 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -374,6 +374,13 @@ is defined): template expansion, the PROBE (or similar) command must be run prior to the macro containing this reference. +## pwm_cycle_time + +The following information is available in +[pwm_cycle_time some_name](Config_Reference.md#pwm_cycle_time) +objects: +- `value`: The "value" of the pin, as set by a `SET_PIN` command. + ## quad_gantry_level The following information is available in the `quad_gantry_level` object diff --git a/klippy/extras/output_pin.py b/klippy/extras/output_pin.py index 76789c37d839..ef094674c1c1 100644 --- a/klippy/extras/output_pin.py +++ b/klippy/extras/output_pin.py @@ -1,6 +1,6 @@ -# Code to configure miscellaneous chips +# PWM and digital output pin handling # -# Copyright (C) 2017-2021 Kevin O'Connor +# Copyright (C) 2017-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. @@ -21,11 +21,9 @@ def __init__(self, config): hardware_pwm = config.getboolean('hardware_pwm', False) self.mcu_pin.setup_cycle_time(cycle_time, hardware_pwm) self.scale = config.getfloat('scale', 1., above=0.) - self.last_cycle_time = self.default_cycle_time = cycle_time else: self.mcu_pin = ppins.setup_pin('digital_out', config.get('pin')) self.scale = 1. - self.last_cycle_time = self.default_cycle_time = 0. self.last_print_time = 0. # Support mcu checking for maximum duration self.reactor = self.printer.get_reactor() @@ -58,32 +56,30 @@ def __init__(self, config): desc=self.cmd_SET_PIN_help) def get_status(self, eventtime): return {'value': self.last_value} - def _set_pin(self, print_time, value, cycle_time, is_resend=False): - if value == self.last_value and cycle_time == self.last_cycle_time: - if not is_resend: - return + def _set_pin(self, print_time, value, is_resend=False): + if value == self.last_value and not is_resend: + return print_time = max(print_time, self.last_print_time + PIN_MIN_TIME) if self.is_pwm: - self.mcu_pin.set_pwm(print_time, value, cycle_time) + self.mcu_pin.set_pwm(print_time, value) else: self.mcu_pin.set_digital(print_time, value) self.last_value = value - self.last_cycle_time = cycle_time self.last_print_time = print_time if self.resend_interval and self.resend_timer is None: self.resend_timer = self.reactor.register_timer( self._resend_current_val, self.reactor.NOW) cmd_SET_PIN_help = "Set the value of an output pin" def cmd_SET_PIN(self, gcmd): + # Read requested value value = gcmd.get_float('VALUE', minval=0., maxval=self.scale) value /= self.scale - cycle_time = gcmd.get_float('CYCLE_TIME', self.default_cycle_time, - above=0., maxval=MAX_SCHEDULE_TIME) if not self.is_pwm and value not in [0., 1.]: raise gcmd.error("Invalid pin value") + # Obtain print_time and apply requested settings toolhead = self.printer.lookup_object('toolhead') toolhead.register_lookahead_callback( - lambda print_time: self._set_pin(print_time, value, cycle_time)) + lambda print_time: self._set_pin(print_time, value)) def _resend_current_val(self, eventtime): if self.last_value == self.shutdown_value: @@ -97,8 +93,7 @@ def _resend_current_val(self, eventtime): if time_diff > 0.: # Reschedule for resend time return systime + time_diff - self._set_pin(print_time + PIN_MIN_TIME, - self.last_value, self.last_cycle_time, True) + self._set_pin(print_time + PIN_MIN_TIME, self.last_value, True) return systime + self.resend_interval def load_config_prefix(config): diff --git a/klippy/extras/pwm_cycle_time.py b/klippy/extras/pwm_cycle_time.py new file mode 100644 index 000000000000..cebbec7512b8 --- /dev/null +++ b/klippy/extras/pwm_cycle_time.py @@ -0,0 +1,123 @@ +# Handle pwm output pins with variable frequency +# +# Copyright (C) 2017-2023 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. + +PIN_MIN_TIME = 0.100 +MAX_SCHEDULE_TIME = 5.0 + +class MCU_pwm_cycle: + def __init__(self, pin_params, cycle_time, start_value, shutdown_value): + self._mcu = pin_params['chip'] + self._cycle_time = cycle_time + self._oid = None + self._mcu.register_config_callback(self._build_config) + self._pin = pin_params['pin'] + self._invert = pin_params['invert'] + if self._invert: + start_value = 1. - start_value + shutdown_value = 1. - shutdown_value + self._start_value = max(0., min(1., start_value)) + self._shutdown_value = max(0., min(1., shutdown_value)) + self._last_clock = self._cycle_ticks = 0 + self._set_cmd = self._set_cycle_ticks = None + def _build_config(self): + cmd_queue = self._mcu.alloc_command_queue() + curtime = self._mcu.get_printer().get_reactor().monotonic() + printtime = self._mcu.estimated_print_time(curtime) + self._last_clock = self._mcu.print_time_to_clock(printtime + 0.200) + cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time) + if self._shutdown_value not in [0., 1.]: + raise self._mcu.get_printer().config_error( + "shutdown value must be 0.0 or 1.0 on soft pwm") + if cycle_ticks >= 1<<31: + raise self._mcu.get_printer().config_error( + "PWM pin cycle time too large") + self._mcu.request_move_queue_slot() + self._oid = self._mcu.create_oid() + self._mcu.add_config_cmd( + "config_digital_out oid=%d pin=%s value=%d" + " default_value=%d max_duration=%d" + % (self._oid, self._pin, self._start_value >= 1.0, + self._shutdown_value >= 0.5, 0)) + self._mcu.add_config_cmd( + "set_digital_out_pwm_cycle oid=%d cycle_ticks=%d" + % (self._oid, cycle_ticks)) + self._cycle_ticks = cycle_ticks + svalue = int(self._start_value * cycle_ticks + 0.5) + self._mcu.add_config_cmd( + "queue_digital_out oid=%d clock=%d on_ticks=%d" + % (self._oid, self._last_clock, svalue), is_init=True) + self._set_cmd = self._mcu.lookup_command( + "queue_digital_out oid=%c clock=%u on_ticks=%u", cq=cmd_queue) + self._set_cycle_ticks = self._mcu.lookup_command( + "set_digital_out_pwm_cycle oid=%c cycle_ticks=%u", cq=cmd_queue) + def set_pwm_cycle(self, print_time, value, cycle_time): + clock = self._mcu.print_time_to_clock(print_time) + minclock = self._last_clock + # Send updated cycle_time if necessary + cycle_ticks = self._mcu.seconds_to_clock(cycle_time) + if cycle_ticks != self._cycle_ticks: + if cycle_ticks >= 1<<31: + raise self._mcu.get_printer().command_error( + "PWM cycle time too large") + self._set_cycle_ticks.send([self._oid, cycle_ticks], + minclock=minclock, reqclock=clock) + self._cycle_ticks = cycle_ticks + # Send pwm update + if self._invert: + value = 1. - value + v = int(max(0., min(1., value)) * float(self._cycle_ticks) + 0.5) + self._set_cmd.send([self._oid, clock, v], + minclock=self._last_clock, reqclock=clock) + self._last_clock = clock + +class PrinterOutputPWMCycle: + def __init__(self, config): + self.printer = config.get_printer() + self.last_print_time = 0. + cycle_time = config.getfloat('cycle_time', 0.100, above=0., + maxval=MAX_SCHEDULE_TIME) + self.last_cycle_time = self.default_cycle_time = cycle_time + # Determine start and shutdown values + self.scale = config.getfloat('scale', 1., above=0.) + self.last_value = config.getfloat( + 'value', 0., minval=0., maxval=self.scale) / self.scale + self.shutdown_value = config.getfloat( + 'shutdown_value', 0., minval=0., maxval=self.scale) / self.scale + # Create pwm pin object + ppins = self.printer.lookup_object('pins') + pin_params = ppins.lookup_pin(config.get('pin'), can_invert=True) + self.mcu_pin = MCU_pwm_cycle(pin_params, cycle_time, + self.last_value, self.shutdown_value) + # Register commands + pin_name = config.get_name().split()[1] + gcode = self.printer.lookup_object('gcode') + gcode.register_mux_command("SET_PIN", "PIN", pin_name, + self.cmd_SET_PIN, + desc=self.cmd_SET_PIN_help) + def get_status(self, eventtime): + return {'value': self.last_value} + def _set_pin(self, print_time, value, cycle_time): + if value == self.last_value and cycle_time == self.last_cycle_time: + return + print_time = max(print_time, self.last_print_time + PIN_MIN_TIME) + self.mcu_pin.set_pwm_cycle(print_time, value, cycle_time) + self.last_value = value + self.last_cycle_time = cycle_time + self.last_print_time = print_time + cmd_SET_PIN_help = "Set the value of an output pin" + def cmd_SET_PIN(self, gcmd): + # Read requested value + value = gcmd.get_float('VALUE', minval=0., maxval=self.scale) + value /= self.scale + cycle_time = gcmd.get_float('CYCLE_TIME', self.default_cycle_time, + above=0., maxval=MAX_SCHEDULE_TIME) + # Obtain print_time and apply requested settings + toolhead = self.printer.lookup_object('toolhead') + toolhead.register_lookahead_callback( + lambda print_time: self._set_pin(print_time, value, cycle_time)) + +def load_config_prefix(config): + return PrinterOutputPWMCycle(config) diff --git a/test/klippy/pwm.cfg b/test/klippy/pwm.cfg index fbda912694ac..af5b5b10ec45 100644 --- a/test/klippy/pwm.cfg +++ b/test/klippy/pwm.cfg @@ -5,6 +5,12 @@ value: 0 shutdown_value: 0 cycle_time: 0.01 +[pwm_cycle_time cycle_pwm_pin] +pin: PH7 +value: 0 +shutdown_value: 0 +cycle_time: 0.01 + [output_pin hard_pwm_pin] pin: PH6 pwm: True diff --git a/test/klippy/pwm.test b/test/klippy/pwm.test index 5e74a3e0569e..fdbf42f2acf8 100644 --- a/test/klippy/pwm.test +++ b/test/klippy/pwm.test @@ -16,18 +16,24 @@ SET_PIN PIN=soft_pwm_pin VALUE=0 SET_PIN PIN=soft_pwm_pin VALUE=0.5 SET_PIN PIN=soft_pwm_pin VALUE=1 +# Soft PWM with dynamic cycle time +# Test basic on off +SET_PIN PIN=cycle_pwm_pin VALUE=0 +SET_PIN PIN=cycle_pwm_pin VALUE=0.5 +SET_PIN PIN=cycle_pwm_pin VALUE=1 + # Test cycle time -SET_PIN PIN=soft_pwm_pin VALUE=0 CYCLE_TIME=0.1 -SET_PIN PIN=soft_pwm_pin VALUE=1 CYCLE_TIME=0.5 -SET_PIN PIN=soft_pwm_pin VALUE=0.5 CYCLE_TIME=0.001 -SET_PIN PIN=soft_pwm_pin VALUE=0.75 CYCLE_TIME=0.01 -SET_PIN PIN=soft_pwm_pin VALUE=0.5 CYCLE_TIME=1 +SET_PIN PIN=cycle_pwm_pin VALUE=0 CYCLE_TIME=0.1 +SET_PIN PIN=cycle_pwm_pin VALUE=1 CYCLE_TIME=0.5 +SET_PIN PIN=cycle_pwm_pin VALUE=0.5 CYCLE_TIME=0.001 +SET_PIN PIN=cycle_pwm_pin VALUE=0.75 CYCLE_TIME=0.01 +SET_PIN PIN=cycle_pwm_pin VALUE=0.5 CYCLE_TIME=1 # Test duplicate values -SET_PIN PIN=soft_pwm_pin VALUE=0.5 CYCLE_TIME=0.5 -SET_PIN PIN=soft_pwm_pin VALUE=0.5 CYCLE_TIME=0.5 -SET_PIN PIN=soft_pwm_pin VALUE=0.75 CYCLE_TIME=0.5 -SET_PIN PIN=soft_pwm_pin VALUE=0.75 CYCLE_TIME=0.75 +SET_PIN PIN=cycle_pwm_pin VALUE=0.5 CYCLE_TIME=0.5 +SET_PIN PIN=cycle_pwm_pin VALUE=0.5 CYCLE_TIME=0.5 +SET_PIN PIN=cycle_pwm_pin VALUE=0.75 CYCLE_TIME=0.5 +SET_PIN PIN=cycle_pwm_pin VALUE=0.75 CYCLE_TIME=0.75 # PWM tool # Basic test From 43a9685c581a46ea161faa297c0a29f3bcd7194e Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 15 Jan 2023 17:14:14 -0500 Subject: [PATCH 025/190] mcu: Remove support for set_pwm() cycle_time parameter Signed-off-by: Kevin O'Connor --- klippy/extras/multi_pin.py | 4 ++-- klippy/extras/replicape.py | 2 +- klippy/extras/sx1509.py | 2 +- klippy/mcu.py | 37 +++++++++---------------------------- 4 files changed, 13 insertions(+), 32 deletions(-) diff --git a/klippy/extras/multi_pin.py b/klippy/extras/multi_pin.py index f5177bd97534..c834ee077f04 100644 --- a/klippy/extras/multi_pin.py +++ b/klippy/extras/multi_pin.py @@ -46,9 +46,9 @@ def setup_cycle_time(self, cycle_time, hardware_pwm=False): def set_digital(self, print_time, value): for mcu_pin in self.mcu_pins: mcu_pin.set_digital(print_time, value) - def set_pwm(self, print_time, value, cycle_time=None): + def set_pwm(self, print_time, value): for mcu_pin in self.mcu_pins: - mcu_pin.set_pwm(print_time, value, cycle_time) + mcu_pin.set_pwm(print_time, value) def load_config_prefix(config): return PrinterMultiPin(config) diff --git a/klippy/extras/replicape.py b/klippy/extras/replicape.py index d6737a35998c..ab501cafc427 100644 --- a/klippy/extras/replicape.py +++ b/klippy/extras/replicape.py @@ -67,7 +67,7 @@ def _build_config(self): cmd_queue = self._mcu.alloc_command_queue() self._set_cmd = self._mcu.lookup_command( "queue_pca9685_out oid=%c clock=%u value=%hu", cq=cmd_queue) - def set_pwm(self, print_time, value, cycle_time=None): + def set_pwm(self, print_time, value): clock = self._mcu.print_time_to_clock(print_time) if self._invert: value = 1. - value diff --git a/klippy/extras/sx1509.py b/klippy/extras/sx1509.py index 51080fe24d6e..3bfecb7831c3 100644 --- a/klippy/extras/sx1509.py +++ b/klippy/extras/sx1509.py @@ -183,7 +183,7 @@ def setup_start_value(self, start_value, shutdown_value): shutdown_value = 1. - shutdown_value self._start_value = max(0., min(1., start_value)) self._shutdown_value = max(0., min(1., shutdown_value)) - def set_pwm(self, print_time, value, cycle_time=None): + def set_pwm(self, print_time, value): self._sx1509.set_register(self._i_on_reg, ~int(255 * value) if not self._invert else int(255 * value) & 0xFF) diff --git a/klippy/mcu.py b/klippy/mcu.py index 7f784de7f21c..f9b547c94479 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -381,9 +381,9 @@ def __init__(self, mcu, pin_params): self._pin = pin_params['pin'] self._invert = pin_params['invert'] self._start_value = self._shutdown_value = float(self._invert) - self._last_clock = self._last_cycle_ticks = 0 + self._last_clock = 0 self._pwm_max = 0. - self._set_cmd = self._set_cycle_ticks = None + self._set_cmd = None def get_mcu(self): return self._mcu def setup_max_duration(self, max_duration): @@ -441,40 +441,21 @@ def _build_config(self): self._mcu.add_config_cmd( "set_digital_out_pwm_cycle oid=%d cycle_ticks=%d" % (self._oid, cycle_ticks)) - self._last_cycle_ticks = cycle_ticks + self._pwm_max = float(cycle_ticks) svalue = int(self._start_value * cycle_ticks + 0.5) self._mcu.add_config_cmd( "queue_digital_out oid=%d clock=%d on_ticks=%d" % (self._oid, self._last_clock, svalue), is_init=True) self._set_cmd = self._mcu.lookup_command( "queue_digital_out oid=%c clock=%u on_ticks=%u", cq=cmd_queue) - self._set_cycle_ticks = self._mcu.lookup_command( - "set_digital_out_pwm_cycle oid=%c cycle_ticks=%u", cq=cmd_queue) - def set_pwm(self, print_time, value, cycle_time=None): - clock = self._mcu.print_time_to_clock(print_time) - minclock = self._last_clock - self._last_clock = clock + def set_pwm(self, print_time, value): if self._invert: value = 1. - value - if self._hardware_pwm: - v = int(max(0., min(1., value)) * self._pwm_max + 0.5) - self._set_cmd.send([self._oid, clock, v], - minclock=minclock, reqclock=clock) - return - # Soft pwm update - if cycle_time is None: - cycle_time = self._cycle_time - cycle_ticks = self._mcu.seconds_to_clock(cycle_time) - if cycle_ticks != self._last_cycle_ticks: - if cycle_ticks >= 1<<31: - raise self._mcu.get_printer().command_error( - "PWM cycle time too large") - self._set_cycle_ticks.send([self._oid, cycle_ticks], - minclock=minclock, reqclock=clock) - self._last_cycle_ticks = cycle_ticks - on_ticks = int(max(0., min(1., value)) * float(cycle_ticks) + 0.5) - self._set_cmd.send([self._oid, clock, on_ticks], - minclock=minclock, reqclock=clock) + v = int(max(0., min(1., value)) * self._pwm_max + 0.5) + clock = self._mcu.print_time_to_clock(print_time) + self._set_cmd.send([self._oid, clock, v], + minclock=self._last_clock, reqclock=clock) + self._last_clock = clock class MCU_adc: def __init__(self, mcu, pin_params): From 55e46aa6250382367af30d3a5e005e919d772d32 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 20 Jan 2024 19:30:21 -0500 Subject: [PATCH 026/190] armcm_boot: Avoid invoking functions in reset_handler_stage_two() Avoid calling memset() and memcpy() prior to copying the ram and clearing the bss. Also, place both ResetHandler() and reset_handler_stage_two() in an explicit ".text.armcm_boot" linker section. These changes make it easier to support targets that want to run all code in ram. Signed-off-by: Kevin O'Connor --- src/generic/armcm_boot.c | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/generic/armcm_boot.c b/src/generic/armcm_boot.c index f83ca60de0ff..9d2ce0bbfe5e 100644 --- a/src/generic/armcm_boot.c +++ b/src/generic/armcm_boot.c @@ -22,7 +22,31 @@ extern uint32_t _stack_end; * Basic interrupt handlers ****************************************************************/ -static void __noreturn +// Inlined version of memset (to avoid function calls during intial boot code) +static void __always_inline +boot_memset(void *s, int c, size_t n) +{ + volatile uint32_t *p = s; + while (n) { + *p++ = c; + n -= sizeof(*p); + } +} + +// Inlined version of memcpy (to avoid function calls during intial boot code) +static void __always_inline +boot_memcpy(void *dest, const void *src, size_t n) +{ + const uint32_t *s = src; + volatile uint32_t *d = dest; + while (n) { + *d++ = *s++; + n -= sizeof(*d); + } +} + +// Main initialization code (called from ResetHandler below) +static void __noreturn __section(".text.armcm_boot.stage_two") reset_handler_stage_two(void) { int i; @@ -60,10 +84,10 @@ reset_handler_stage_two(void) // Copy global variables from flash to ram uint32_t count = (&_data_end - &_data_start) * 4; - __builtin_memcpy(&_data_start, &_data_flash, count); + boot_memcpy(&_data_start, &_data_flash, count); // Clear the bss segment - __builtin_memset(&_bss_start, 0, (&_bss_end - &_bss_start) * 4); + boot_memset(&_bss_start, 0, (&_bss_end - &_bss_start) * 4); barrier(); @@ -80,7 +104,7 @@ reset_handler_stage_two(void) // Initial code entry point - invoked by the processor after a reset // Reset interrupts and stack to take control from bootloaders -void +void __section(".text.armcm_boot.stage_one") ResetHandler(void) { __disable_irq(); From 23c5b20f5ba6bbb27c3b5404f7b0912dba511eb5 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 20 Jan 2024 19:58:23 -0500 Subject: [PATCH 027/190] rp2040: Always link using rp2040_link.lds.S Use the rp2040 specific linker script even when using a bootloader. Signed-off-by: Kevin O'Connor --- src/rp2040/Makefile | 2 +- src/rp2040/rp2040_link.lds.S | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/rp2040/Makefile b/src/rp2040/Makefile index 71ed90a0cc0a..64199014009a 100644 --- a/src/rp2040/Makefile +++ b/src/rp2040/Makefile @@ -55,7 +55,7 @@ $(OUT)klipper.bin: $(OUT)klipper.elf $(Q)$(OBJCOPY) -O binary $< $@ rptarget-$(CONFIG_RP2040_HAVE_BOOTLOADER) := $(OUT)klipper.bin -rplink-$(CONFIG_RP2040_HAVE_BOOTLOADER) := $(OUT)src/generic/armcm_link.ld +rplink-$(CONFIG_RP2040_HAVE_BOOTLOADER) := $(OUT)src/rp2040/rp2040_link.ld # Set klipper.elf linker rules target-y += $(rptarget-y) diff --git a/src/rp2040/rp2040_link.lds.S b/src/rp2040/rp2040_link.lds.S index 43d6115e4983..2052cdbd42fd 100644 --- a/src/rp2040/rp2040_link.lds.S +++ b/src/rp2040/rp2040_link.lds.S @@ -1,6 +1,6 @@ // rp2040 linker script (based on armcm_link.lds.S and customized for stage2) // -// Copyright (C) 2019-2021 Kevin O'Connor +// Copyright (C) 2019-2024 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -9,9 +9,15 @@ OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) +#if CONFIG_RP2040_HAVE_STAGE2 + #define ROM_ORIGIN 0x10000000 +#else + #define ROM_ORIGIN CONFIG_FLASH_APPLICATION_ADDRESS +#endif + MEMORY { - rom (rx) : ORIGIN = 0x10000000 , LENGTH = CONFIG_FLASH_SIZE + rom (rx) : ORIGIN = ROM_ORIGIN , LENGTH = CONFIG_FLASH_SIZE ram (rwx) : ORIGIN = CONFIG_RAM_START , LENGTH = CONFIG_RAM_SIZE } @@ -19,7 +25,9 @@ SECTIONS { .text : { . = ALIGN(4); +#if CONFIG_RP2040_HAVE_STAGE2 KEEP(*(.boot2)) +#endif _text_vectortable_start = .; KEEP(*(.vector_table)) _text_vectortable_end = .; From 44e79e0c37a440212a1b7f974adbdbe250e91f83 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 20 Jan 2024 19:33:21 -0500 Subject: [PATCH 028/190] rp2040: Run all code from ram Place all normal code into ram. This reduces the chance that rp2040 instruction cache misses could cause subtle timing issues. Signed-off-by: Kevin O'Connor --- src/rp2040/rp2040_link.lds.S | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/rp2040/rp2040_link.lds.S b/src/rp2040/rp2040_link.lds.S index 2052cdbd42fd..fd178847c9f1 100644 --- a/src/rp2040/rp2040_link.lds.S +++ b/src/rp2040/rp2040_link.lds.S @@ -31,8 +31,7 @@ SECTIONS _text_vectortable_start = .; KEEP(*(.vector_table)) _text_vectortable_end = .; - *(.text .text.*) - *(.rodata .rodata*) + *(.text.armcm_boot*) } > rom . = ALIGN(4); @@ -42,7 +41,9 @@ SECTIONS { . = ALIGN(4); _data_start = .; + *(.text .text.*) *(.ramfunc .ramfunc.*); + *(.rodata .rodata*) *(.data .data.*); . = ALIGN(4); _data_end = .; From f1982edcd5e68328a824ae9998e63778b08581e7 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 20 Jan 2024 20:04:16 -0500 Subject: [PATCH 029/190] rp2040: Load vectortable into ram Load the interrupt vector table into ram at startup. This reduces the chance of a flash cache access causing timing instability. Signed-off-by: Kevin O'Connor --- src/rp2040/main.c | 21 +++++++++++++++++++++ src/rp2040/rp2040_link.lds.S | 6 ++++++ 2 files changed, 27 insertions(+) diff --git a/src/rp2040/main.c b/src/rp2040/main.c index 0b144d0bba0c..e7b64e5f0da2 100644 --- a/src/rp2040/main.c +++ b/src/rp2040/main.c @@ -16,6 +16,26 @@ #include "sched.h" // sched_main +/**************************************************************** + * Ram IRQ vector table + ****************************************************************/ + +// Copy vector table to ram and activate it +static void +enable_ram_vectortable(void) +{ + // Symbols created by rp2040_link.lds.S linker script + extern uint32_t _ram_vectortable_start, _ram_vectortable_end; + extern uint32_t _text_vectortable_start; + + uint32_t count = (&_ram_vectortable_end - &_ram_vectortable_start) * 4; + __builtin_memcpy(&_ram_vectortable_start, &_text_vectortable_start, count); + barrier(); + + SCB->VTOR = (uint32_t)&_ram_vectortable_start; +} + + /**************************************************************** * Bootloader ****************************************************************/ @@ -145,6 +165,7 @@ clock_setup(void) void armcm_main(void) { + enable_ram_vectortable(); clock_setup(); sched_main(); } diff --git a/src/rp2040/rp2040_link.lds.S b/src/rp2040/rp2040_link.lds.S index fd178847c9f1..9b0264a2b941 100644 --- a/src/rp2040/rp2040_link.lds.S +++ b/src/rp2040/rp2040_link.lds.S @@ -37,6 +37,12 @@ SECTIONS . = ALIGN(4); _data_flash = .; + .ram_vectortable (NOLOAD) : { + _ram_vectortable_start = .; + . = . + ( _text_vectortable_end - _text_vectortable_start ) ; + _ram_vectortable_end = .; + } > ram + .data : AT (_data_flash) { . = ALIGN(4); From 5e433fff06148fde3f0046ad7f1121e9af2181d9 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 22 Jan 2024 12:55:22 -0500 Subject: [PATCH 030/190] rp2040: Only change SPI settings while peripheral is disabled Make sure to disable/enable the peripheral to ensure the clock polarity is properly set prior to a change in CS. Signed-off-by: Kevin O'Connor --- src/rp2040/spi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/rp2040/spi.c b/src/rp2040/spi.c index e6aafa0053a4..758d57308b79 100644 --- a/src/rp2040/spi.c +++ b/src/rp2040/spi.c @@ -89,8 +89,12 @@ void spi_prepare(struct spi_config config) { spi_hw_t *spi = config.spi; + if (spi->cr0 == config.cr0 && spi->cpsr == config.cpsr) + return; + spi->cr1 = 0; spi->cr0 = config.cr0; spi->cpsr = config.cpsr; + spi->cr1 = SPI_SSPCR1_SSE_BITS; } void From 5e3daa6f21d6485e4e757d0df00e01a13c968541 Mon Sep 17 00:00:00 2001 From: voidtrance <30448940+voidtrance@users.noreply.github.com> Date: Fri, 26 Jan 2024 14:50:01 -0800 Subject: [PATCH 031/190] bed_mesh: Implement adaptive bed mesh (#6461) Adaptive bed mesh allows the bed mesh algorithm to probe only the area of the bed that is being used by the current print. It uses [exclude_objects] to get a list of the printed objects and their area on the bed. It, then, modifies the bed mesh parameters so only the area used by the objects is measured. Adaptive bed mesh works on both cartesian and delta kinematics printers. On Delta printers, the algorithm, adjusts the origin point and radius in order to translate the area of the bed being probe. Signed-off-by: Mitko Haralanov Signed-off-by: Kyle Hansen Signed-off-by: Kevin O'Connor --- docs/Bed_Mesh.md | 60 +++++++++++++- docs/Config_Reference.md | 3 + docs/img/adaptive_bed_mesh.svg | 4 + docs/img/adaptive_bed_mesh_margin.svg | 4 + klippy/extras/bed_mesh.py | 114 +++++++++++++++++++++++++- 5 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 docs/img/adaptive_bed_mesh.svg create mode 100644 docs/img/adaptive_bed_mesh_margin.svg diff --git a/docs/Bed_Mesh.md b/docs/Bed_Mesh.md index d2a417dd40ec..ada3de29d249 100644 --- a/docs/Bed_Mesh.md +++ b/docs/Bed_Mesh.md @@ -370,14 +370,68 @@ are identified in green. ![bedmesh_interpolated](img/bedmesh_faulty_regions.svg) +### Adaptive Meshes + +Adaptive bed meshing is a way to speed up the bed mesh generation by only probing +the area of the bed used by the objects being printed. When used, the method will +automatically adjust the mesh parameters based on the area occupied by the defined +print objects. + +The adapted mesh area will be computed from the area defined by the boundaries of all +the defined print objects so it covers every object, including any margins defined in +the configuration. After the area is computed, the number of probe points will be +scaled down based on the ratio of the default mesh area and the adapted mesh area. To +illustrate this consider the following example: + +For a 150mmx150mm bed with `mesh_min` set to `25,25` and `mesh_max` set to `125,125`, +the default mesh area is a 100mmx100mm square. An adapted mesh area of `50,50` +means a ratio of `0.5x0.5` between the adapted area and default mesh area. + +If the `bed_mesh` configuration specified `probe_count` as `7x7`, the adapted bed +mesh will use 4x4 probe points (7 * 0.5 rounded up). + +![adaptive_bedmesh](img/adaptive_bed_mesh.svg) + +``` +[bed_mesh] +speed: 120 +horizontal_move_z: 5 +mesh_min: 35, 6 +mesh_max: 240, 198 +probe_count: 5, 3 +adaptive_margin: 5 +``` + +- `adaptive_margin` \ + _Default Value: 0_ \ + Margin (in mm) to add around the area of the bed used by the defined objects. The diagram + below shows the adapted bed mesh area with an `adaptive_margin` of 5mm. The adapted mesh + area (area in green) is computed as the used bed area (area in blue) plus the defined margin. + + ![adaptive_bedmesh_margin](img/adaptive_bed_mesh_margin.svg) + +By nature, adaptive bed meshes use the objects defined by the Gcode file being printed. +Therefore, it is expected that each Gcode file will generate a mesh that probes a different +area of the print bed. Therefore, adapted bed meshes should not be re-used. The expectation +is that a new mesh will be generated for each print if adaptive meshing is used. + +It is also important to consider that adaptive bed meshing is best used on machines that can +normally probe the entire bed and achieve a maximum variance less than or equal to 1 layer +height. Machines with mechanical issues that a full bed mesh normally compensates for may +have undesirable results when attempting print moves **outside** of the probed area. If a +full bed mesh has a variance greater than 1 layer height, caution must be taken when using +adaptive bed meshes and attempting print moves outside of the meshed area. + ## Bed Mesh Gcodes ### Calibration `BED_MESH_CALIBRATE PROFILE= METHOD=[manual | automatic] [=] - [=]`\ + [=] [ADAPTIVE=[0|1] [ADAPTIVE_MARGIN=]`\ _Default Profile: default_\ -_Default Method: automatic if a probe is detected, otherwise manual_ +_Default Method: automatic if a probe is detected, otherwise manual_ \ +_Default Adaptive: 0_ \ +_Default Adaptive Margin: 0_ Initiates the probing procedure for Bed Mesh Calibration. @@ -399,6 +453,8 @@ following parameters are available: - `ROUND_PROBE_COUNT` - All beds: - `ALGORITHM` + - `ADAPTIVE` + - `ADAPTIVE_MARGIN` See the configuration documentation above for details on how each parameter applies to the mesh. diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 3b2f17709dd6..985408091946 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -991,6 +991,9 @@ Visual Examples: # Optional points that define a faulty region. See docs/Bed_Mesh.md # for details on faulty regions. Up to 99 faulty regions may be added. # By default no faulty regions are set. +#adaptive_margin: +# An optional margin (in mm) to be added around the bed area used by +# the defined print objects when generating an adaptive mesh. ``` ### [bed_tilt] diff --git a/docs/img/adaptive_bed_mesh.svg b/docs/img/adaptive_bed_mesh.svg new file mode 100644 index 000000000000..954ca0b322c7 --- /dev/null +++ b/docs/img/adaptive_bed_mesh.svg @@ -0,0 +1,4 @@ + + + +
Origin
(0,0)
Origin...

Legend


Legend
Object Polygon
Object Polygon
Used Bed Area
Used Bed Area
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/img/adaptive_bed_mesh_margin.svg b/docs/img/adaptive_bed_mesh_margin.svg new file mode 100644 index 000000000000..6c6216d041aa --- /dev/null +++ b/docs/img/adaptive_bed_mesh_margin.svg @@ -0,0 +1,4 @@ + + + +
Origin
(0,0)
Origin...

Legend


Legend
Object Polygon
Object Polygon
Used Bed Area
Used Bed Area
Adapted Bed Mesh Area
Adapted Bed Mesh Area
(90,75)
(90,75)
(130,140)
(130,140)
(125,135)
(125,1...
(95,120)
(95,12...
(60,90)
(60,90)
(90,130)
(90,13...
(95,80)
(95,80)
(125,110)
(125,1...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index 6c714304fb8a..9e30846f0ffd 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -1,6 +1,5 @@ # Mesh Bed Leveling # -# Copyright (C) 2018 Kevin O'Connor # Copyright (C) 2018-2019 Eric Callahan # # This file may be distributed under the terms of the GNU GPLv3 license. @@ -291,6 +290,7 @@ def __init__(self, config, bedmesh): self.orig_config = {'radius': None, 'origin': None} self.radius = self.origin = None self.mesh_min = self.mesh_max = (0., 0.) + self.adaptive_margin = config.getfloat('adaptive_margin', 0.0) self.zero_ref_pos = config.getfloatlist( "zero_reference_position", None, count=2 ) @@ -573,6 +573,113 @@ def _verify_algorithm(self, error): "interpolation. Configured Probe Count: %d, %d" % (self.mesh_config['x_count'], self.mesh_config['y_count'])) params['algo'] = 'lagrange' + def set_adaptive_mesh(self, gcmd): + if not gcmd.get_int('ADAPTIVE', 0): + return False + exclude_objects = self.printer.lookup_object("exclude_object", None) + if exclude_objects is None: + gcmd.respond_info("Exclude objects not enabled. Using full mesh...") + return False + objects = exclude_objects.get_status().get("objects", []) + if not objects: + return False + margin = gcmd.get_float('ADAPTIVE_MARGIN', self.adaptive_margin) + + # List all exclude_object points by axis and iterate over + # all polygon points, and pick the min and max or each axis + list_of_xs = [] + list_of_ys = [] + gcmd.respond_info("Found %s objects" % (len(objects))) + for obj in objects: + for point in obj["polygon"]: + list_of_xs.append(point[0]) + list_of_ys.append(point[1]) + + # Define bounds of adaptive mesh area + mesh_min = [min(list_of_xs), min(list_of_ys)] + mesh_max = [max(list_of_xs), max(list_of_ys)] + adjusted_mesh_min = [x - margin for x in mesh_min] + adjusted_mesh_max = [x + margin for x in mesh_max] + + # Force margin to respect original mesh bounds + adjusted_mesh_min[0] = max(adjusted_mesh_min[0], + self.orig_config["mesh_min"][0]) + adjusted_mesh_min[1] = max(adjusted_mesh_min[1], + self.orig_config["mesh_min"][1]) + adjusted_mesh_max[0] = min(adjusted_mesh_max[0], + self.orig_config["mesh_max"][0]) + adjusted_mesh_max[1] = min(adjusted_mesh_max[1], + self.orig_config["mesh_max"][1]) + + adjusted_mesh_size = (adjusted_mesh_max[0] - adjusted_mesh_min[0], + adjusted_mesh_max[1] - adjusted_mesh_min[1]) + + # Compute a ratio between the adapted and original sizes + ratio = (adjusted_mesh_size[0] / + (self.orig_config["mesh_max"][0] - + self.orig_config["mesh_min"][0]), + adjusted_mesh_size[1] / + (self.orig_config["mesh_max"][1] - + self.orig_config["mesh_min"][1])) + + gcmd.respond_info("Original mesh bounds: (%s,%s)" % + (self.orig_config["mesh_min"], + self.orig_config["mesh_max"])) + gcmd.respond_info("Original probe count: (%s,%s)" % + (self.mesh_config["x_count"], + self.mesh_config["y_count"])) + gcmd.respond_info("Adapted mesh bounds: (%s,%s)" % + (adjusted_mesh_min, adjusted_mesh_max)) + gcmd.respond_info("Ratio: (%s, %s)" % ratio) + + new_x_probe_count = int( + math.ceil(self.mesh_config["x_count"] * ratio[0])) + new_y_probe_count = int( + math.ceil(self.mesh_config["y_count"] * ratio[1])) + + # There is one case, where we may have to adjust the probe counts: + # axis0 < 4 and axis1 > 6 (see _verify_algorithm). + min_num_of_probes = 3 + if max(new_x_probe_count, new_y_probe_count) > 6 and \ + min(new_x_probe_count, new_y_probe_count) < 4: + min_num_of_probes = 4 + + new_x_probe_count = max(min_num_of_probes, new_x_probe_count) + new_y_probe_count = max(min_num_of_probes, new_y_probe_count) + + gcmd.respond_info("Adapted probe count: (%s,%s)" % + (new_x_probe_count, new_y_probe_count)) + + # If the adapted mesh size is too small, adjust it to something + # useful. + adjusted_mesh_size = (max(adjusted_mesh_size[0], new_x_probe_count), + max(adjusted_mesh_size[1], new_y_probe_count)) + + if self.radius is not None: + adapted_radius = math.sqrt((adjusted_mesh_size[0] ** 2) + + (adjusted_mesh_size[1] ** 2)) / 2 + adapted_origin = (adjusted_mesh_min[0] + + (adjusted_mesh_size[0] / 2), + adjusted_mesh_min[1] + + (adjusted_mesh_size[1] / 2)) + to_adapted_origin = math.sqrt(adapted_origin[0]**2 + + adapted_origin[1]**2) + # If the adapted mesh size is smaller than the default/full + # mesh, adjust the parameters. Otherwise, just do the full mesh. + if adapted_radius + to_adapted_origin < self.radius: + self.radius = adapted_radius + self.origin = adapted_origin + self.mesh_min = (-self.radius, -self.radius) + self.mesh_max = (self.radius, self.radius) + self.mesh_config["x_count"] = self.mesh_config["y_count"] = \ + max(new_x_probe_count, new_y_probe_count) + else: + self.mesh_min = adjusted_mesh_min + self.mesh_max = adjusted_mesh_max + self.mesh_config["x_count"] = new_x_probe_count + self.mesh_config["y_count"] = new_y_probe_count + self._profile_name = None + return True def update_config(self, gcmd): # reset default configuration self.radius = self.orig_config['radius'] @@ -616,6 +723,8 @@ def update_config(self, gcmd): self.mesh_config['algo'] = gcmd.get('ALGORITHM').strip().lower() need_cfg_update = True + need_cfg_update |= self.set_adaptive_mesh(gcmd) + if need_cfg_update: self._verify_algorithm(gcmd.error) self._generate_points(gcmd.error) @@ -781,7 +890,8 @@ def probe_finalize(self, offsets, positions): z_mesh.set_zero_reference(*self.zero_ref_pos) self.bedmesh.set_mesh(z_mesh) self.gcode.respond_info("Mesh Bed Leveling Complete") - self.bedmesh.save_profile(self._profile_name) + if self._profile_name is not None: + self.bedmesh.save_profile(self._profile_name) def _dump_points(self, probed_pts, corrected_pts, offsets): # logs generated points with offset applied, points received # from the finalize callback, and the list of corrected points From 600e89ae8c759613a3c6fc2b24d0a62d00e6baf2 Mon Sep 17 00:00:00 2001 From: Kiswich Date: Sat, 27 Jan 2024 22:23:50 +0800 Subject: [PATCH 032/190] virtual_sdcard: fix virtual SD file position count (#6472) Signed-off-by: Zhang Qiwei --- klippy/extras/virtual_sdcard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/klippy/extras/virtual_sdcard.py b/klippy/extras/virtual_sdcard.py index 31f283ff256b..1bb914ab22c4 100644 --- a/klippy/extras/virtual_sdcard.py +++ b/klippy/extras/virtual_sdcard.py @@ -258,7 +258,7 @@ def work_handler(self, eventtime): # Dispatch command self.cmd_from_sd = True line = lines.pop() - next_file_position = self.file_position + len(line) + 1 + next_file_position = self.file_position + len(line.encode()) + 1 self.next_file_position = next_file_position try: self.gcode.run_script(line) From 16a7b50ce9866d1bec07899391cd123e7f6aaa44 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Fri, 12 Jan 2024 16:47:55 -0500 Subject: [PATCH 033/190] bed_mesh: fix manual mode point generation Do not generate points for the zero_reference_position or faulty_regions when manual probing is requested. Signed-off-by: Eric Callahan --- klippy/extras/bed_mesh.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index 9e30846f0ffd..bc386c3870eb 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -323,7 +323,7 @@ def __init__(self, config, bedmesh): self.gcode.register_command( 'BED_MESH_CALIBRATE', self.cmd_BED_MESH_CALIBRATE, desc=self.cmd_BED_MESH_CALIBRATE_help) - def _generate_points(self, error): + def _generate_points(self, error, probe_method="automatic"): x_cnt = self.mesh_config['x_count'] y_cnt = self.mesh_config['y_count'] min_x, min_y = self.mesh_min @@ -372,7 +372,7 @@ def _generate_points(self, error): if rri >= len(self.points): raise error("bed_mesh: relative reference index out of range") self.zero_ref_pos = points[rri] - if self.zero_ref_pos is None: + if self.zero_ref_pos is None or probe_method == "manual": # Zero Reference Disabled self.zero_reference_mode = ZrefMode.DISABLED elif within(self.zero_ref_pos, self.mesh_min, self.mesh_max): @@ -398,6 +398,8 @@ def _generate_points(self, error): % (self.zero_ref_pos[0], self.zero_ref_pos[1], opt,) ) # Check to see if any points fall within faulty regions + if probe_method == "manual": + return last_y = self.points[0][1] is_reversed = False for i, coord in enumerate(self.points): @@ -724,10 +726,11 @@ def update_config(self, gcmd): need_cfg_update = True need_cfg_update |= self.set_adaptive_mesh(gcmd) + probe_method = gcmd.get("METHOD", "automatic") if need_cfg_update: self._verify_algorithm(gcmd.error) - self._generate_points(gcmd.error) + self._generate_points(gcmd.error, probe_method) gcmd.respond_info("Generating new points...") self.print_generated_points(gcmd.respond_info) pts = self._get_adjusted_points() @@ -738,7 +741,7 @@ def update_config(self, gcmd): in self.mesh_config.items()]) logging.info("Updated Mesh Configuration:\n" + msg) else: - self._generate_points(gcmd.error) + self._generate_points(gcmd.error, probe_method) pts = self._get_adjusted_points() self.probe_helper.update_probe_points(pts, 3) def _get_adjusted_points(self): @@ -769,7 +772,7 @@ def probe_finalize(self, offsets, positions): x_offset, y_offset, z_offset = offsets positions = [[round(p[0], 2), round(p[1], 2), p[2]] for p in positions] - if self.zero_reference_mode == ZrefMode.PROBE : + if self.zero_reference_mode == ZrefMode.PROBE: ref_pos = positions.pop() logging.info( "bed_mesh: z-offset replaced with probed z value at " From 9f41f53c5e364694b9b41279b3b3aee34250b93a Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Sat, 27 Jan 2024 11:44:39 -0500 Subject: [PATCH 034/190] bed_mesh: fix profile_name reporting in get_status() Adaptive meshing avoids saving the mesh after calibration to prevent users from inadvertently overwriting an existing profile with an adaptive mesh. This introduced a change in behavior of how get_status() reports the profile_name, as it can now be an empty string when a mesh is active. This patch assigns adaptive meshes a name with a unique postfix. In addition, it moves profile name tracking from the profile manager to the ZMesh class. Signed-off-by: Eric Callahan --- klippy/extras/bed_mesh.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index bc386c3870eb..afedb6aafc87 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -233,7 +233,7 @@ def update_status(self): mesh_max = (params['max_x'], params['max_y']) probed_matrix = self.z_mesh.get_probed_matrix() mesh_matrix = self.z_mesh.get_mesh_matrix() - self.status['profile_name'] = self.pmgr.get_current_profile() + self.status['profile_name'] = self.z_mesh.get_profile_name() self.status['mesh_min'] = mesh_min self.status['mesh_max'] = mesh_max self.status['probed_matrix'] = probed_matrix @@ -314,7 +314,7 @@ def __init__(self, config, bedmesh): self.mesh_config = collections.OrderedDict() self._init_mesh_config(config) self._generate_points(config.error) - self._profile_name = None + self._profile_name = "default" self.probe_helper = probe.ProbePointsHelper( config, self.probe_finalize, self._get_adjusted_points()) self.probe_helper.minimum_points(3) @@ -881,7 +881,7 @@ def probe_finalize(self, offsets, positions): "Probed table length: %d Probed Table:\n%s") % (len(probed_matrix), str(probed_matrix))) - z_mesh = ZMesh(params) + z_mesh = ZMesh(params, self._profile_name) try: z_mesh.build_mesh(probed_matrix) except BedMeshError as e: @@ -980,7 +980,8 @@ def split(self): class ZMesh: - def __init__(self, params): + def __init__(self, params, name): + self.profile_name = name or "adaptive-%X" % (id(self),) self.probed_matrix = self.mesh_matrix = None self.mesh_params = params self.mesh_offsets = [0., 0.] @@ -1029,6 +1030,8 @@ def get_probed_matrix(self): return [[]] def get_mesh_params(self): return self.mesh_params + def get_profile_name(self): + return self.profile_name def print_probed_matrix(self, print_func): if self.probed_matrix is not None: msg = "Mesh Leveling Probed Z positions:\n" @@ -1289,7 +1292,6 @@ def __init__(self, config, bedmesh): self.gcode = self.printer.lookup_object('gcode') self.bedmesh = bedmesh self.profiles = {} - self.current_profile = "" self.incompatible_profiles = [] # Fetch stored profiles from Config stored_profs = config.get_prefix_sections(self.name) @@ -1323,8 +1325,6 @@ def __init__(self, config, bedmesh): desc=self.cmd_BED_MESH_PROFILE_help) def get_profiles(self): return self.profiles - def get_current_profile(self): - return self.current_profile def _check_incompatible_profiles(self): if self.incompatible_profiles: configfile = self.printer.lookup_object('configfile') @@ -1365,7 +1365,6 @@ def save_profile(self, prof_name): profile['points'] = probed_matrix profile['mesh_params'] = collections.OrderedDict(mesh_params) self.profiles = profiles - self.current_profile = prof_name self.bedmesh.update_status() self.gcode.respond_info( "Bed Mesh state has been saved to profile [%s]\n" @@ -1379,12 +1378,11 @@ def load_profile(self, prof_name): "bed_mesh: Unknown profile [%s]" % prof_name) probed_matrix = profile['points'] mesh_params = profile['mesh_params'] - z_mesh = ZMesh(mesh_params) + z_mesh = ZMesh(mesh_params, prof_name) try: z_mesh.build_mesh(probed_matrix) except BedMeshError as e: raise self.gcode.error(str(e)) - self.current_profile = prof_name self.bedmesh.set_mesh(z_mesh) def remove_profile(self, prof_name): if prof_name in self.profiles: From 6ce6fbbce07605010c0d4fa8eaa0ebcde01e53db Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 9 Feb 2024 19:21:34 -0500 Subject: [PATCH 035/190] docs: Fix typo in Probe_Calibrate.md Reported by @nmattia. Signed-off-by: Kevin O'Connor --- docs/Probe_Calibrate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Probe_Calibrate.md b/docs/Probe_Calibrate.md index ed3964e68923..5f1d44365d91 100644 --- a/docs/Probe_Calibrate.md +++ b/docs/Probe_Calibrate.md @@ -64,7 +64,7 @@ automatic probe point, then `ABORT` the manual probe tool and perform the XY probe offset calibration described above. Once the manual probe tool starts, follow the steps described at -["the paper test"](Bed_Level.md#the-paper-test)) to determine the +["the paper test"](Bed_Level.md#the-paper-test) to determine the actual distance between the nozzle and bed at the given location. Once those steps are complete one can `ACCEPT` the position and save the results to the config file with: From 1b24f6a2ad2b7527f5fc70efaf9a4055f4bef752 Mon Sep 17 00:00:00 2001 From: Anders Brujordet Date: Wed, 14 Feb 2024 01:18:08 +0100 Subject: [PATCH 036/190] docs: Add required dependency to run numpy with python3 on RPI (#6491) Signed-off-by: Anders Brujordet --- docs/Measuring_Resonances.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Measuring_Resonances.md b/docs/Measuring_Resonances.md index 28cda9d0c656..79f7de0f95e1 100644 --- a/docs/Measuring_Resonances.md +++ b/docs/Measuring_Resonances.md @@ -207,7 +207,7 @@ software dependencies not installed by default. First, run on your Raspberry Pi the following commands: ``` sudo apt update -sudo apt install python3-numpy python3-matplotlib libatlas-base-dev +sudo apt install python3-numpy python3-matplotlib libatlas-base-dev libopenblas-base ``` Next, in order to install NumPy in the Klipper environment, run the command: From 0cd16e956d1e3aa54ba8e8b6cb677139c797f3ef Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Mon, 5 Feb 2024 16:53:09 -0500 Subject: [PATCH 037/190] bed_mesh: add ZFADE parameter to BED_MESH_OFFSET When a ZFADE value is passed to BED_MESH_OFFSET it is used to adjust how fade is applied. This resolves issues with fade when SET_GCODE_OFFSET is used during a tool change. Signed-off-by: Eric Callahan --- klippy/extras/bed_mesh.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index afedb6aafc87..a962ff16beb6 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -102,6 +102,7 @@ def __init__(self, config): self.log_fade_complete = False self.base_fade_target = config.getfloat('fade_target', None) self.fade_target = 0. + self.tool_offset = 0. self.gcode = self.printer.lookup_object('gcode') self.splitter = MoveSplitter(config, self.gcode) # setup persistent storage @@ -157,6 +158,7 @@ def set_mesh(self, mesh): "mesh max: %.4f" % (self.fade_dist, min_z, max_z)) else: self.fade_target = 0. + self.tool_offset = 0. self.z_mesh = mesh self.splitter.initialize(mesh, self.fade_target) # cache the current position before a transform takes place @@ -164,6 +166,7 @@ def set_mesh(self, mesh): gcode_move.reset_last_position() self.update_status() def get_z_factor(self, z_pos): + z_pos += self.tool_offset if z_pos >= self.fade_end: return 0. elif z_pos >= self.fade_start: @@ -182,14 +185,15 @@ def get_position(self): max_adj = self.z_mesh.calc_z(x, y) factor = 1. z_adj = max_adj - self.fade_target - if min(z, (z - max_adj)) >= self.fade_end: + fade_z_pos = z + self.tool_offset + if min(fade_z_pos, (fade_z_pos - max_adj)) >= self.fade_end: # Fade out is complete, no factor factor = 0. - elif max(z, (z - max_adj)) >= self.fade_start: + elif max(fade_z_pos, (fade_z_pos - max_adj)) >= self.fade_start: # Likely in the process of fading out adjustment. # Because we don't yet know the gcode z position, use # algebra to calculate the factor from the toolhead pos - factor = ((self.fade_end + self.fade_target - z) / + factor = ((self.fade_end + self.fade_target - fade_z_pos) / (self.fade_dist - z_adj)) factor = constrain(factor, 0., 1.) final_z_adj = factor * z_adj + self.fade_target @@ -271,6 +275,9 @@ def cmd_BED_MESH_OFFSET(self, gcmd): for i, axis in enumerate(['X', 'Y']): offsets[i] = gcmd.get_float(axis, None) self.z_mesh.set_mesh_offsets(offsets) + tool_offset = gcmd.get_float("ZFADE", None) + if tool_offset is not None: + self.tool_offset = tool_offset gcode_move = self.printer.lookup_object('gcode_move') gcode_move.reset_last_position() else: From 0aaabf1904aebcd6940fc15cc19015c6853317f8 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Tue, 6 Feb 2024 11:19:03 -0500 Subject: [PATCH 038/190] docs: update BED_MESH_OFFSET description Add the ZFADE parameter to the documentation. Signed-off-by: Eric Callahan --- docs/Bed_Mesh.md | 14 +++++++++++--- docs/G-Codes.md | 10 ++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/Bed_Mesh.md b/docs/Bed_Mesh.md index ada3de29d249..9ee8df507e77 100644 --- a/docs/Bed_Mesh.md +++ b/docs/Bed_Mesh.md @@ -542,11 +542,19 @@ This gcode may be used to clear the internal mesh state. ### Apply X/Y offsets -`BED_MESH_OFFSET [X=] [Y=]` +`BED_MESH_OFFSET [X=] [Y=] [ZFADE=]` This is useful for printers with multiple independent extruders, as an offset is necessary to produce correct Z adjustment after a tool change. Offsets should be specified relative to the primary extruder. That is, a positive X offset should be specified if the secondary extruder is mounted to the -right of the primary extruder, and a positive Y offset should be specified -if the secondary extruder is mounted "behind" the primary extruder. +right of the primary extruder, a positive Y offset should be specified +if the secondary extruder is mounted "behind" the primary extruder, and +a positive ZFADE offset should be specified if the secondary extruder's +nozzle is above the primary extruder's. + +Note that a ZFADE offset does *NOT* directly apply additional adjustment. It +is intended to compensate for a `gcode offset` when [mesh fade](#mesh-fade) +is enabled. For example, if a secondary extruder is higher than the primary +and needs a negative gcode offset, ie: `SET_GCODE_OFFSET Z=-.2`, it can be +accounted for in `bed_mesh` with `BED_MESH_OFFSET ZFADE=.2`. diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 92cb76606afb..3e32ba3f7cd9 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -184,10 +184,12 @@ SAVE_CONFIG gcode must be run to make the changes to persistent memory permanent. #### BED_MESH_OFFSET -`BED_MESH_OFFSET [X=] [Y=]`: Applies X and/or Y offsets -to the mesh lookup. This is useful for printers with independent -extruders, as an offset is necessary to produce correct Z adjustment -after a tool change. +`BED_MESH_OFFSET [X=] [Y=] [ZFADE= Date: Thu, 15 Feb 2024 12:28:19 -0500 Subject: [PATCH 039/190] docs: Fix typo in Skew_Correction.md Reported by @streetgt. Signed-off-by: Kevin O'Connor --- docs/Skew_Correction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Skew_Correction.md b/docs/Skew_Correction.md index 0475de89ae66..55bc22b0a5cc 100644 --- a/docs/Skew_Correction.md +++ b/docs/Skew_Correction.md @@ -21,7 +21,7 @@ or by issuing a `SET_SKEW CLEAR=1` gcode. ## Take your measurements -The `[skew_correcton]` module requires 3 measurements for each plane you want +The `[skew_correction]` module requires 3 measurements for each plane you want to correct; the length from Corner A to Corner C, the length from Corner B to Corner D, and the length from Corner A to Corner D. When measuring length AD do not include the flats on the corners that some test objects provide. From b2ac0f1ce33bab9e09abe789643ac3b8568f4297 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 13 Jan 2024 14:00:14 -0500 Subject: [PATCH 040/190] heaters: Remove deprecated thermistor "NTC 100K beta 3950" Signed-off-by: Kevin O'Connor --- klippy/extras/heaters.py | 2 -- klippy/extras/temperature_sensors.cfg | 6 ------ 2 files changed, 8 deletions(-) diff --git a/klippy/extras/heaters.py b/klippy/extras/heaters.py index 0e9411c9f6eb..1c29aae81a16 100644 --- a/klippy/extras/heaters.py +++ b/klippy/extras/heaters.py @@ -281,8 +281,6 @@ def setup_sensor(self, config): if sensor_type not in self.sensor_factories: raise self.printer.config_error( "Unknown temperature sensor '%s'" % (sensor_type,)) - if sensor_type == 'NTC 100K beta 3950': - config.deprecate('sensor_type', 'NTC 100K beta 3950') return self.sensor_factories[sensor_type](config) def register_sensor(self, config, psensor, gcode_id=None): self.available_sensors.append(config.get_name()) diff --git a/klippy/extras/temperature_sensors.cfg b/klippy/extras/temperature_sensors.cfg index 96aa996401ae..107fcd24b663 100644 --- a/klippy/extras/temperature_sensors.cfg +++ b/klippy/extras/temperature_sensors.cfg @@ -102,12 +102,6 @@ temperature1: 25 resistance1: 100000 beta: 3974 -# Definition inherent from name. This sensor is deprecated! -[thermistor NTC 100K beta 3950] -temperature1: 25 -resistance1: 100000 -beta: 3950 - # Definition from description of Marlin "thermistor 75" [thermistor NTC 100K MGB18-104F39050L32] temperature1: 25 From 2f7b234189c95a4a18088f36d1976c87780ee634 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 13 Jan 2024 14:00:50 -0500 Subject: [PATCH 041/190] extruder: Remove deprecated commands and config Remove the deprecated SET_EXTRUDER_STEP_DISTANCE and SYNC_STEPPER_TO_EXTRUDER commands. Remove the deprecated shared_heater config option. Signed-off-by: Kevin O'Connor --- config/printer-geeetech-301-2019.cfg | 12 ++++------- docs/G-Codes.md | 6 ------ klippy/kinematics/extruder.py | 31 +--------------------------- 3 files changed, 5 insertions(+), 44 deletions(-) diff --git a/config/printer-geeetech-301-2019.cfg b/config/printer-geeetech-301-2019.cfg index 5b3a9a4ab25e..6889fae5ae08 100644 --- a/config/printer-geeetech-301-2019.cfg +++ b/config/printer-geeetech-301-2019.cfg @@ -71,25 +71,21 @@ pid_Kp: 39 pid_Ki: 2 pid_Kd: 210 -[extruder1] +[extruder_stepper e1] +extruder: step_pin: PA0 dir_pin: !PB6 enable_pin: !PA1 microsteps: 16 rotation_distance: 32 -nozzle_diameter: 0.4 -filament_diameter: 1.75 -shared_heater: extruder -[extruder2] +[extruder_stepper e2] +extruder: step_pin: PB2 dir_pin: !PB11 enable_pin: !PC4 microsteps: 16 rotation_distance: 32 -nozzle_diameter: 0.4 -filament_diameter: 1.75 -shared_heater: extruder [heater_bed] heater_pin: PB1 diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 3e32ba3f7cd9..5a8cd920c98d 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -449,12 +449,6 @@ MOTION_QUEUE (as defined in an [extruder](Config_Reference.md#extruder) config section). If MOTION_QUEUE is an empty string then the stepper will be desynchronized from all extruder movement. -#### SET_EXTRUDER_STEP_DISTANCE -This command is deprecated and will be removed in the near future. - -#### SYNC_STEPPER_TO_EXTRUDER -This command is deprecated and will be removed in the near future. - ### [fan_generic] The following command is available when a diff --git a/klippy/kinematics/extruder.py b/klippy/kinematics/extruder.py index 4fe041c5b5e3..6924003783e8 100644 --- a/klippy/kinematics/extruder.py +++ b/klippy/kinematics/extruder.py @@ -38,12 +38,6 @@ def __init__(self, config): gcode.register_mux_command("SYNC_EXTRUDER_MOTION", "EXTRUDER", self.name, self.cmd_SYNC_EXTRUDER_MOTION, desc=self.cmd_SYNC_EXTRUDER_MOTION_help) - gcode.register_mux_command("SET_EXTRUDER_STEP_DISTANCE", "EXTRUDER", - self.name, self.cmd_SET_E_STEP_DISTANCE, - desc=self.cmd_SET_E_STEP_DISTANCE_help) - gcode.register_mux_command("SYNC_STEPPER_TO_EXTRUDER", "STEPPER", - self.name, self.cmd_SYNC_STEPPER_TO_EXTRUDER, - desc=self.cmd_SYNC_STEPPER_TO_EXTRUDER_help) def _handle_connect(self): toolhead = self.printer.lookup_object('toolhead') toolhead.register_step_generator(self.stepper.generate_steps) @@ -133,24 +127,6 @@ def cmd_SYNC_EXTRUDER_MOTION(self, gcmd): self.sync_to_extruder(ename) gcmd.respond_info("Extruder '%s' now syncing with '%s'" % (self.name, ename)) - cmd_SET_E_STEP_DISTANCE_help = "Set extruder step distance" - def cmd_SET_E_STEP_DISTANCE(self, gcmd): - step_dist = gcmd.get_float('DISTANCE', None, above=0.) - if step_dist is not None: - toolhead = self.printer.lookup_object('toolhead') - toolhead.flush_step_generation() - rd, steps_per_rotation = self.stepper.get_rotation_distance() - self.stepper.set_rotation_distance(step_dist * steps_per_rotation) - else: - step_dist = self.stepper.get_step_dist() - gcmd.respond_info("Extruder '%s' step distance set to %0.6f" - % (self.name, step_dist)) - cmd_SYNC_STEPPER_TO_EXTRUDER_help = "Set extruder stepper" - def cmd_SYNC_STEPPER_TO_EXTRUDER(self, gcmd): - ename = gcmd.get('EXTRUDER') - self.sync_to_extruder(ename) - gcmd.respond_info("Extruder '%s' now syncing with '%s'" - % (self.name, ename)) # Tracking for hotend heater, extrusion motion queue, and extruder stepper class PrinterExtruder: @@ -159,14 +135,9 @@ def __init__(self, config, extruder_num): self.name = config.get_name() self.last_position = 0. # Setup hotend heater - shared_heater = config.get('shared_heater', None) pheaters = self.printer.load_object(config, 'heaters') gcode_id = 'T%d' % (extruder_num,) - if shared_heater is None: - self.heater = pheaters.setup_heater(config, gcode_id) - else: - config.deprecate('shared_heater') - self.heater = pheaters.lookup_heater(shared_heater) + self.heater = pheaters.setup_heater(config, gcode_id) # Setup kinematic checks self.nozzle_diameter = config.getfloat('nozzle_diameter', above=0.) filament_diameter = config.getfloat( From c92732e4f18caf95d1a2789f8438cca4c844413d Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 13 Jan 2024 14:04:38 -0500 Subject: [PATCH 042/190] bed_mesh: Remove deprecated relative_reference_index Signed-off-by: Kevin O'Connor --- config/printer-modix-big60-2020.cfg | 1 - docs/Config_Reference.md | 7 ----- klippy/extras/bed_mesh.py | 42 +++++------------------------ 3 files changed, 6 insertions(+), 44 deletions(-) diff --git a/config/printer-modix-big60-2020.cfg b/config/printer-modix-big60-2020.cfg index 0224851ea4b9..2fc311cf19dc 100644 --- a/config/printer-modix-big60-2020.cfg +++ b/config/printer-modix-big60-2020.cfg @@ -199,7 +199,6 @@ algorithm: bicubic bicubic_tension: 0.15 fade_start: 0.5 fade_end: 2.5 -relative_reference_index: 60 [bed_screws] screw1: 0,0 diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 985408091946..14b89200fb00 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -979,13 +979,6 @@ Visual Examples: # where Z = 0. When this option is specified the mesh will be offset # so that zero Z adjustment occurs at this location. The default is # no zero reference. -#relative_reference_index: -# **DEPRECATED, use the "zero_reference_position" option** -# The legacy option superceded by the "zero reference position". -# Rather than a coordinate this option takes an integer "index" that -# refers to the location of one of the generated points. It is recommended -# to use the "zero_reference_position" instead of this option for new -# configurations. The default is no relative reference index. #faulty_region_1_min: #faulty_region_1_max: # Optional points that define a faulty region. See docs/Bed_Mesh.md diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index a962ff16beb6..87f2324a4376 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -301,19 +301,6 @@ def __init__(self, config, bedmesh): self.zero_ref_pos = config.getfloatlist( "zero_reference_position", None, count=2 ) - self.relative_reference_index = config.getint( - 'relative_reference_index', None, minval=0) - config.deprecate('relative_reference_index') - if ( - self.zero_ref_pos is not None and - self.relative_reference_index is not None - ): - self.relative_reference_index = None - logging.info( - "bed_mesh: both 'zero_reference_postion' and " - "'relative_reference_index' options are specified, " - "the 'zero_reference_position' value will be used." - ) self.zero_reference_mode = ZrefMode.DISABLED self.faulty_regions = [] self.substituted_indices = collections.OrderedDict() @@ -373,12 +360,6 @@ def _generate_points(self, error, probe_method="automatic"): (self.origin[0] + pos_x, self.origin[1] + pos_y)) pos_y += y_dist self.points = points - rri = self.relative_reference_index - if self.zero_ref_pos is None and rri is not None: - # Zero ref position needs to be initialized - if rri >= len(self.points): - raise error("bed_mesh: relative reference index out of range") - self.zero_ref_pos = points[rri] if self.zero_ref_pos is None or probe_method == "manual": # Zero Reference Disabled self.zero_reference_mode = ZrefMode.DISABLED @@ -396,8 +377,6 @@ def _generate_points(self, error, probe_method="automatic"): for min_c, max_c in self.faulty_regions: if within(self.zero_ref_pos, min_c, max_c): opt = "zero_reference_position" - if self.relative_reference_index is not None: - opt = "relative_reference_index" raise error( "bed_mesh: Cannot probe zero reference position at " "(%.2f, %.2f) as it is located within a faulty region." @@ -456,17 +435,10 @@ def print_generated_points(self, print_func): print_func( " %-4d| %-16s| %s" % (i, adj_pt, mesh_pt)) if self.zero_ref_pos is not None: - rri = self.relative_reference_index - if rri is not None: - print_func( - "bed_mesh: relative_reference_index %d is (%.2f, %.2f)" - % (rri, self.zero_ref_pos[0], self.zero_ref_pos[1]) - ) - else: - print_func( - "bed_mesh: zero_reference_position is (%.2f, %.2f)" - % (self.zero_ref_pos[0], self.zero_ref_pos[1]) - ) + print_func( + "bed_mesh: zero_reference_position is (%.2f, %.2f)" + % (self.zero_ref_pos[0], self.zero_ref_pos[1]) + ) if self.substituted_indices: print_func("bed_mesh: faulty region points") for i, v in self.substituted_indices.items(): @@ -742,10 +714,8 @@ def update_config(self, gcmd): self.print_generated_points(gcmd.respond_info) pts = self._get_adjusted_points() self.probe_helper.update_probe_points(pts, 3) - msg = "relative_reference_index: %s\n" % \ - (self.relative_reference_index) - msg += "\n".join(["%s: %s" % (k, v) for k, v - in self.mesh_config.items()]) + msg = "\n".join(["%s: %s" % (k, v) + for k, v in self.mesh_config.items()]) logging.info("Updated Mesh Configuration:\n" + msg) else: self._generate_points(gcmd.error, probe_method) From 4f00f2199174eca1b022fe4b8f7f3aaedfd90b6f Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 13 Jan 2024 14:23:40 -0500 Subject: [PATCH 043/190] docs: Note removal of deprecated options in Config_Changes.md Signed-off-by: Kevin O'Connor --- docs/Config_Changes.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index ae2c5f0a8ccd..980c4f33ac7f 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,14 @@ All dates in this document are approximate. ## Changes +20240215: Several deprecated features have been removed. Using "NTC +100K beta 3950" as a thermistor name has been removed (deprecated on +20211110). The `SYNC_STEPPER_TO_EXTRUDER` and +`SET_EXTRUDER_STEP_DISTANCE` commands have been removed, and the +extruder `shared_heater` config option has been removed (deprecated on +20220210). The bed_mesh `relative_reference_index` option has been +removed (deprecated on 20230619). + 20240123: The output_pin SET_PIN CYCLE_TIME parameter has been removed. Use the new [pwm_cycle_time](Config_Reference.md#pwm_cycle_time) module if it is From 72b301a2859c3f7ed26d802dd52fc495eef6c353 Mon Sep 17 00:00:00 2001 From: Dmitry Butyugin Date: Thu, 8 Feb 2024 03:06:48 +0100 Subject: [PATCH 044/190] scripts: Added shaper tuning parameters to calibrate_shaper script The added parameters include square_corner_velocity, shaper frequencies to optimize, input shapers to test, input shaper damping ratio and damping ratios to test. All these options can be useful for fine-tuning the input shapers when the default suggestions generated by the tuning script are not optimal. Also the `SHAPER_CALIBRATE` command was modified to pass some of these parameters to the shaper tuning routine. Specifically, square corner velocity and the maximum tested frequency are used to adjust shaper tuning and maximum acceleration recommendations. Signed-off-by: Dmitry Butyugin --- docs/Measuring_Resonances.md | 13 +++++ klippy/extras/resonance_tester.py | 12 +++- klippy/extras/shaper_calibrate.py | 52 ++++++++++++----- scripts/calibrate_shaper.py | 93 ++++++++++++++++++++++++++++--- 4 files changed, 145 insertions(+), 25 deletions(-) diff --git a/docs/Measuring_Resonances.md b/docs/Measuring_Resonances.md index 79f7de0f95e1..d1d998941dac 100644 --- a/docs/Measuring_Resonances.md +++ b/docs/Measuring_Resonances.md @@ -662,6 +662,19 @@ The same notice applies to the input shaper `max_accel` value after the auto-calibration, and the suggested acceleration limits will not be applied automatically. +Keep in mind that the maximum acceleration without too much smoothing depends +on the `square_corner_velocity`. The general recommendation is not to change +it from its default value 5.0, and this is the value used by default by the +`calibrate_shaper.py` script. If you did change it though, you should inform +the script about it by passing `--square_corner_velocity=...` parameter, e.g. +``` +~/klipper/scripts/calibrate_shaper.py /tmp/resonances_x_*.csv -o /tmp/shaper_calibrate_x.png --square_corner_velocity=10.0 +``` +so that it can calculate the maximum acceleration recommendations correctly. +Note that the `SHAPER_CALIBRATE` command already takes the configured +`square_corner_velocity` parameter into account, and there is no need +to specify it explicitly. + If you are doing a shaper re-calibration and the reported smoothing for the suggested shaper configuration is almost the same as what you got during the previous calibration, this step can be skipped. diff --git a/klippy/extras/resonance_tester.py b/klippy/extras/resonance_tester.py index d5f43b77f531..4b29d6b80072 100644 --- a/klippy/extras/resonance_tester.py +++ b/klippy/extras/resonance_tester.py @@ -1,6 +1,6 @@ # A utility class to test resonances of the printer # -# Copyright (C) 2020 Dmitry Butyugin +# Copyright (C) 2020-2024 Dmitry Butyugin # # This file may be distributed under the terms of the GNU GPLv3 license. import logging, math, os, time @@ -114,6 +114,8 @@ def run_test(self, axis, gcmd): if input_shaper is not None: input_shaper.enable_shaping() gcmd.respond_info("Re-enabled [input_shaper]") + def get_max_freq(self): + return self.freq_end class ResonanceTester: def __init__(self, config): @@ -302,8 +304,14 @@ def cmd_SHAPER_CALIBRATE(self, gcmd): "Calculating the best input shaper parameters for %s axis" % (axis_name,)) calibration_data[axis].normalize_to_frequencies() + systime = self.printer.get_reactor().monotonic() + toolhead = self.printer.lookup_object('toolhead') + toolhead_info = toolhead.get_status(systime) + scv = toolhead_info['square_corner_velocity'] best_shaper, all_shapers = helper.find_best_shaper( - calibration_data[axis], max_smoothing, gcmd.respond_info) + calibration_data[axis], max_smoothing=max_smoothing, + scv=scv, max_freq=1.5*self.test.get_max_freq(), + logging=gcmd.respond_info) gcmd.respond_info( "Recommended shaper_type_%s = %s, shaper_freq_%s = %.1f Hz" % (axis_name, best_shaper.name, diff --git a/klippy/extras/shaper_calibrate.py b/klippy/extras/shaper_calibrate.py index af77845c5cd5..f3bfd8d2a905 100644 --- a/klippy/extras/shaper_calibrate.py +++ b/klippy/extras/shaper_calibrate.py @@ -1,6 +1,6 @@ # Automatic calibration of input shapers # -# Copyright (C) 2020 Dmitry Butyugin +# Copyright (C) 2020-2024 Dmitry Butyugin # # This file may be distributed under the terms of the GNU GPLv3 license. import collections, importlib, logging, math, multiprocessing, traceback @@ -227,34 +227,49 @@ def _get_shaper_smoothing(self, shaper, accel=5000, scv=5.): offset_180 *= inv_D return max(offset_90, offset_180) - def fit_shaper(self, shaper_cfg, calibration_data, max_smoothing): + def fit_shaper(self, shaper_cfg, calibration_data, shaper_freqs, + damping_ratio, scv, max_smoothing, test_damping_ratios, + max_freq): np = self.numpy - test_freqs = np.arange(shaper_cfg.min_freq, MAX_SHAPER_FREQ, .2) + damping_ratio = damping_ratio or shaper_defs.DEFAULT_DAMPING_RATIO + test_damping_ratios = test_damping_ratios or TEST_DAMPING_RATIOS + + if not shaper_freqs: + shaper_freqs = (None, None, None) + if isinstance(shaper_freqs, tuple): + freq_end = shaper_freqs[1] or MAX_SHAPER_FREQ + freq_start = min(shaper_freqs[0] or shaper_cfg.min_freq, + freq_end - 1e-7) + freq_step = shaper_freqs[2] or .2 + test_freqs = np.arange(freq_start, freq_end, freq_step) + else: + test_freqs = np.array(shaper_freqs) + + max_freq = max(max_freq or MAX_FREQ, test_freqs.max()) freq_bins = calibration_data.freq_bins - psd = calibration_data.psd_sum[freq_bins <= MAX_FREQ] - freq_bins = freq_bins[freq_bins <= MAX_FREQ] + psd = calibration_data.psd_sum[freq_bins <= max_freq] + freq_bins = freq_bins[freq_bins <= max_freq] best_res = None results = [] for test_freq in test_freqs[::-1]: shaper_vibrations = 0. shaper_vals = np.zeros(shape=freq_bins.shape) - shaper = shaper_cfg.init_func( - test_freq, shaper_defs.DEFAULT_DAMPING_RATIO) - shaper_smoothing = self._get_shaper_smoothing(shaper) + shaper = shaper_cfg.init_func(test_freq, damping_ratio) + shaper_smoothing = self._get_shaper_smoothing(shaper, scv=scv) if max_smoothing and shaper_smoothing > max_smoothing and best_res: return best_res # Exact damping ratio of the printer is unknown, pessimizing # remaining vibrations over possible damping values - for dr in TEST_DAMPING_RATIOS: + for dr in test_damping_ratios: vibrations, vals = self._estimate_remaining_vibrations( shaper, dr, freq_bins, psd) shaper_vals = np.maximum(shaper_vals, vals) if vibrations > shaper_vibrations: shaper_vibrations = vibrations - max_accel = self.find_shaper_max_accel(shaper) + max_accel = self.find_shaper_max_accel(shaper, scv) # The score trying to minimize vibrations, but also accounting # the growth of smoothing. The formula itself does not have any # special meaning, it simply shows good results on real user data @@ -278,6 +293,8 @@ def fit_shaper(self, shaper_cfg, calibration_data, max_smoothing): def _bisect(self, func): left = right = 1. + if not func(1e-9): + return 0. while not func(left): right = left left *= .5 @@ -292,22 +309,27 @@ def _bisect(self, func): right = middle return left - def find_shaper_max_accel(self, shaper): + def find_shaper_max_accel(self, shaper, scv): # Just some empirically chosen value which produces good projections # for max_accel without much smoothing TARGET_SMOOTHING = 0.12 max_accel = self._bisect(lambda test_accel: self._get_shaper_smoothing( - shaper, test_accel) <= TARGET_SMOOTHING) + shaper, test_accel, scv) <= TARGET_SMOOTHING) return max_accel - def find_best_shaper(self, calibration_data, max_smoothing, logger=None): + def find_best_shaper(self, calibration_data, shapers=None, + damping_ratio=None, scv=None, shaper_freqs=None, + max_smoothing=None, test_damping_ratios=None, + max_freq=None, logger=None): best_shaper = None all_shapers = [] + shapers = shapers or AUTOTUNE_SHAPERS for shaper_cfg in shaper_defs.INPUT_SHAPERS: - if shaper_cfg.name not in AUTOTUNE_SHAPERS: + if shaper_cfg.name not in shapers: continue shaper = self.background_process_exec(self.fit_shaper, ( - shaper_cfg, calibration_data, max_smoothing)) + shaper_cfg, calibration_data, shaper_freqs, damping_ratio, + scv, max_smoothing, test_damping_ratios, max_freq)) if logger is not None: logger("Fitted shaper '%s' frequency = %.1f Hz " "(vibrations = %.1f%%, smoothing ~= %.3f)" % ( diff --git a/scripts/calibrate_shaper.py b/scripts/calibrate_shaper.py index 8a0fcdf00b6b..b56ce5daa836 100755 --- a/scripts/calibrate_shaper.py +++ b/scripts/calibrate_shaper.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # Shaper auto-calibration script # -# Copyright (C) 2020 Dmitry Butyugin +# Copyright (C) 2020-2024 Dmitry Butyugin # Copyright (C) 2020 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. @@ -40,7 +40,9 @@ def parse_log(logname): ###################################################################### # Find the best shaper parameters -def calibrate_shaper(datas, csv_output, max_smoothing): +def calibrate_shaper(datas, csv_output, *, shapers, damping_ratio, scv, + shaper_freqs, max_smoothing, test_damping_ratios, + max_freq): helper = shaper_calibrate.ShaperCalibrate(printer=None) if isinstance(datas[0], shaper_calibrate.CalibrationData): calibration_data = datas[0] @@ -52,8 +54,17 @@ def calibrate_shaper(datas, csv_output, max_smoothing): for data in datas[1:]: calibration_data.add_data(helper.process_accelerometer_data(data)) calibration_data.normalize_to_frequencies() + + shaper, all_shapers = helper.find_best_shaper( - calibration_data, max_smoothing, print) + calibration_data, shapers=shapers, damping_ratio=damping_ratio, + scv=scv, shaper_freqs=shaper_freqs, max_smoothing=max_smoothing, + test_damping_ratios=test_damping_ratios, max_freq=max_freq, + logger=print) + if not shaper: + print("No recommended shaper, possibly invalid value for --shapers=%s" % + (','.join(shapers))) + return None, None, None print("Recommended shaper is %s @ %.1f Hz" % (shaper.name, shaper.freq)) if csv_output is not None: helper.save_calibration_data( @@ -140,28 +151,94 @@ def main(): opts.add_option("-c", "--csv", type="string", dest="csv", default=None, help="filename of output csv file") opts.add_option("-f", "--max_freq", type="float", default=200., - help="maximum frequency to graph") - opts.add_option("-s", "--max_smoothing", type="float", default=None, - help="maximum shaper smoothing to allow") + help="maximum frequency to plot") + opts.add_option("-s", "--max_smoothing", type="float", dest="max_smoothing", + default=None, help="maximum shaper smoothing to allow") + opts.add_option("--scv", "--square_corner_velocity", type="float", + dest="scv", default=5., help="square corner velocity") + opts.add_option("--shaper_freq", type="string", dest="shaper_freq", + default=None, help="shaper frequency(-ies) to test, " + + "either a comma-separated list of floats, or a range in " + + "the format [start]:end[:step]") + opts.add_option("--shapers", type="string", dest="shapers", default=None, + help="a comma-separated list of shapers to test") + opts.add_option("--damping_ratio", type="float", dest="damping_ratio", + default=None, help="shaper damping_ratio parameter") + opts.add_option("--test_damping_ratios", type="string", + dest="test_damping_ratios", default=None, + help="a comma-separated liat of damping ratios to test " + + "input shaper for") options, args = opts.parse_args() if len(args) < 1: opts.error("Incorrect number of arguments") if options.max_smoothing is not None and options.max_smoothing < 0.05: opts.error("Too small max_smoothing specified (must be at least 0.05)") + max_freq = options.max_freq + if options.shaper_freq is None: + shaper_freqs = [] + elif options.shaper_freq.find(':') >= 0: + freq_start = None + freq_end = None + freq_step = None + try: + freqs_parsed = options.shaper_freq.partition(':') + if freqs_parsed[0]: + freq_start = float(freqs_parsed[0]) + freqs_parsed = freqs_parsed[-1].partition(':') + freq_end = float(freqs_parsed[0]) + if freq_start and freq_start > freq_end: + opts.error("Invalid --shaper_freq param: start range larger " + + "than its end") + if freqs_parsed[-1].find(':') >= 0: + opts.error("Invalid --shaper_freq param format") + if freqs_parsed[-1]: + freq_step = float(freqs_parsed[-1]) + except ValueError: + opts.error("--shaper_freq param does not specify correct range " + + "in the format [start]:end[:step]") + shaper_freqs = (freq_start, freq_end, freq_step) + max_freq = max(max_freq, freq_end * 4./3.) + else: + try: + shaper_freqs = [float(s) for s in options.shaper_freq.split(',')] + except ValueError: + opts.error("invalid floating point value in --shaper_freq param") + max_freq = max(max_freq, max(shaper_freqs) * 4./3.) + if options.test_damping_ratios: + try: + test_damping_ratios = [float(s) for s in + options.test_damping_ratios.split(',')] + except ValueError: + opts.error("invalid floating point value in " + + "--test_damping_ratios param") + else: + test_damping_ratios = None + if options.shapers is None: + shapers = None + else: + shapers = options.shapers.lower().split(',') + # Parse data datas = [parse_log(fn) for fn in args] # Calibrate shaper and generate outputs selected_shaper, shapers, calibration_data = calibrate_shaper( - datas, options.csv, options.max_smoothing) + datas, options.csv, shapers=shapers, + damping_ratio=options.damping_ratio, + scv=options.scv, shaper_freqs=shaper_freqs, + max_smoothing=options.max_smoothing, + test_damping_ratios=test_damping_ratios, + max_freq=max_freq) + if selected_shaper is None: + return if not options.csv or options.output: # Draw graph setup_matplotlib(options.output is not None) fig = plot_freq_response(args, calibration_data, shapers, - selected_shaper, options.max_freq) + selected_shaper, max_freq) # Show graph if options.output is None: From 28f06a104bc0cfe3a7d36db343ade5a805b3e132 Mon Sep 17 00:00:00 2001 From: Dmitry Butyugin Date: Sat, 17 Feb 2024 15:14:03 +0100 Subject: [PATCH 045/190] shaper_calibrate: Fixed crashes in SHAPER_CALIBRATE and TEST_RESONANCES Fixed crashes due to wrong parameter passed to the shaper selection function and when the custom FREQ_END is specified. Signed-off-by: Dmitry Butyugin --- klippy/extras/resonance_tester.py | 22 ++++++++++++---------- klippy/extras/shaper_calibrate.py | 6 ++++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/klippy/extras/resonance_tester.py b/klippy/extras/resonance_tester.py index 4b29d6b80072..d249412cadcc 100644 --- a/klippy/extras/resonance_tester.py +++ b/klippy/extras/resonance_tester.py @@ -52,7 +52,7 @@ def __init__(self, config): self.min_freq = config.getfloat('min_freq', 5., minval=1.) # Defaults are such that max_freq * accel_per_hz == 10000 (max_accel) self.max_freq = config.getfloat('max_freq', 10000. / 75., - minval=self.min_freq, maxval=200.) + minval=self.min_freq, maxval=300.) self.accel_per_hz = config.getfloat('accel_per_hz', 75., above=0.) self.hz_per_sec = config.getfloat('hz_per_sec', 1., minval=0.1, maxval=2.) @@ -64,7 +64,7 @@ def get_start_test_points(self): def prepare_test(self, gcmd): self.freq_start = gcmd.get_float("FREQ_START", self.min_freq, minval=1.) self.freq_end = gcmd.get_float("FREQ_END", self.max_freq, - minval=self.freq_start, maxval=200.) + minval=self.freq_start, maxval=300.) self.hz_per_sec = gcmd.get_float("HZ_PER_SEC", self.hz_per_sec, above=0., maxval=2.) def run_test(self, axis, gcmd): @@ -219,6 +219,8 @@ def _parse_chips(self, accel_chips): chip = self.printer.lookup_object(chip_lookup_name) parsed_chips.append(chip) return parsed_chips + def _get_max_calibration_freq(self): + return 1.5 * self.test.get_max_freq() cmd_TEST_RESONANCES_help = ("Runs the resonance test for a specifed axis") def cmd_TEST_RESONANCES(self, gcmd): # Parse parameters @@ -263,9 +265,9 @@ def cmd_TEST_RESONANCES(self, gcmd): raw_name_suffix=name_suffix if raw_output else None, accel_chips=accel_chips, test_point=test_point)[axis] if csv_output: - csv_name = self.save_calibration_data('resonances', name_suffix, - helper, axis, data, - point=test_point) + csv_name = self.save_calibration_data( + 'resonances', name_suffix, helper, axis, data, + point=test_point, max_freq=self._get_max_calibration_freq()) gcmd.respond_info( "Resonances data written to %s file" % (csv_name,)) cmd_SHAPER_CALIBRATE_help = ( @@ -308,10 +310,10 @@ def cmd_SHAPER_CALIBRATE(self, gcmd): toolhead = self.printer.lookup_object('toolhead') toolhead_info = toolhead.get_status(systime) scv = toolhead_info['square_corner_velocity'] + max_freq = self._get_max_calibration_freq() best_shaper, all_shapers = helper.find_best_shaper( calibration_data[axis], max_smoothing=max_smoothing, - scv=scv, max_freq=1.5*self.test.get_max_freq(), - logging=gcmd.respond_info) + scv=scv, max_freq=max_freq, logger=gcmd.respond_info) gcmd.respond_info( "Recommended shaper_type_%s = %s, shaper_freq_%s = %.1f Hz" % (axis_name, best_shaper.name, @@ -323,7 +325,7 @@ def cmd_SHAPER_CALIBRATE(self, gcmd): best_shaper.name, best_shaper.freq) csv_name = self.save_calibration_data( 'calibration_data', name_suffix, helper, axis, - calibration_data[axis], all_shapers) + calibration_data[axis], all_shapers, max_freq=max_freq) gcmd.respond_info( "Shaper calibration data written to %s file" % (csv_name,)) gcmd.respond_info( @@ -369,10 +371,10 @@ def get_filename(self, base, name_suffix, axis=None, def save_calibration_data(self, base_name, name_suffix, shaper_calibrate, axis, calibration_data, - all_shapers=None, point=None): + all_shapers=None, point=None, max_freq=None): output = self.get_filename(base_name, name_suffix, axis, point) shaper_calibrate.save_calibration_data(output, calibration_data, - all_shapers) + all_shapers, max_freq) return output def load_config(config): diff --git a/klippy/extras/shaper_calibrate.py b/klippy/extras/shaper_calibrate.py index f3bfd8d2a905..6891fefb3bf9 100644 --- a/klippy/extras/shaper_calibrate.py +++ b/klippy/extras/shaper_calibrate.py @@ -368,8 +368,10 @@ def apply_params(self, input_shaper, axis, shaper_name, shaper_freq): "SHAPER_TYPE_" + axis: shaper_name, "SHAPER_FREQ_" + axis: shaper_freq})) - def save_calibration_data(self, output, calibration_data, shapers=None): + def save_calibration_data(self, output, calibration_data, shapers=None, + max_freq=None): try: + max_freq = max_freq or MAX_FREQ with open(output, "w") as csvfile: csvfile.write("freq,psd_x,psd_y,psd_z,psd_xyz") if shapers: @@ -378,7 +380,7 @@ def save_calibration_data(self, output, calibration_data, shapers=None): csvfile.write("\n") num_freqs = calibration_data.freq_bins.shape[0] for i in range(num_freqs): - if calibration_data.freq_bins[i] >= MAX_FREQ: + if calibration_data.freq_bins[i] >= max_freq: break csvfile.write("%.1f,%.3e,%.3e,%.3e,%.3e" % ( calibration_data.freq_bins[i], From a77d07907fdfcd76f7175231caee170db205ff04 Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Fri, 16 Feb 2024 12:42:25 +0000 Subject: [PATCH 046/190] docs: updates BED_MESH_CALIBRATE description Adds the ADAPTIVE and ADAPTIVE_MARGIN parameters to the documentation. Signed-off-by: Pedro Lamas --- docs/G-Codes.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 5a8cd920c98d..199dc283b7ce 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -146,15 +146,21 @@ The following commands are available when the (also see the [bed mesh guide](Bed_Mesh.md)). #### BED_MESH_CALIBRATE -`BED_MESH_CALIBRATE [METHOD=manual] [HORIZONTAL_MOVE_Z=] -[=] [=]`: This command probes -the bed using generated points specified by the parameters in the config. After -probing, a mesh is generated and z-movement is adjusted according to the mesh. +`BED_MESH_CALIBRATE [PROFILE=] [METHOD=manual] [HORIZONTAL_MOVE_Z=] +[=] [=] [ADAPTIVE=1] +[ADAPTIVE_MARGIN=]`: This command probes the bed using generated points +specified by the parameters in the config. After probing, a mesh is generated +and z-movement is adjusted according to the mesh. +The mesh will be saved into a profile specified by the `PROFILE` parameter, +or `default` if unspecified. See the PROBE command for details on the optional probe parameters. If METHOD=manual is specified then the manual probing tool is activated - see the MANUAL_PROBE command above for details on the additional commands available while this tool is active. The optional `HORIZONTAL_MOVE_Z` value overrides the -`horizontal_move_z` option specified in the config file. +`horizontal_move_z` option specified in the config file. If ADAPTIVE=1 is +specified then the objects defined by the Gcode file being printed will be used +to define the probed area. The optional `ADAPTIVE_MARGIN` value overrides the +`adaptive_margin` option specified in the config file. #### BED_MESH_OUTPUT `BED_MESH_OUTPUT PGP=[<0:1>]`: This command outputs the current probed From b98375b360b45a92b2281005877ed7255d080a0c Mon Sep 17 00:00:00 2001 From: Jake Date: Mon, 26 Feb 2024 21:27:49 -0500 Subject: [PATCH 047/190] avr: enable small code size options for atmega32u4 Signed-off-by: Jake Beju --- src/avr/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/avr/Kconfig b/src/avr/Kconfig index f1ad54395442..d4d78d279392 100644 --- a/src/avr/Kconfig +++ b/src/avr/Kconfig @@ -11,7 +11,7 @@ config AVR_SELECT select HAVE_GPIO_I2C select HAVE_GPIO_HARD_PWM select HAVE_STRICT_TIMING - select HAVE_LIMITED_CODE_SIZE if MACH_atmega168 || MACH_atmega328 || MACH_atmega328p + select HAVE_LIMITED_CODE_SIZE if MACH_atmega168 || MACH_atmega328 || MACH_atmega328p || MACH_atmega32u4 config BOARD_DIRECTORY string From 31de734d193dca3395803f7bd146476688547ebe Mon Sep 17 00:00:00 2001 From: Derek Kaser Date: Mon, 4 Mar 2024 12:15:25 -0500 Subject: [PATCH 048/190] config: update Kobra Plus build instructions and fan settings Signed-off-by: Derek Kaser --- config/printer-anycubic-kobra-plus-2022.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/printer-anycubic-kobra-plus-2022.cfg b/config/printer-anycubic-kobra-plus-2022.cfg index 96099797042c..3f71caab81b5 100644 --- a/config/printer-anycubic-kobra-plus-2022.cfg +++ b/config/printer-anycubic-kobra-plus-2022.cfg @@ -14,6 +14,7 @@ # To build the firmware, use the following configuration: # - Micro-controller: Huada Semiconductor HC32F460 # - Communication interface: Serial (PA3 & PA2) - Anycube +# - Clock Speed: 200 MHz # # Installation: # 1. Rename the klipper bin to `firmware.bin` and copy it to an SD Card. @@ -144,10 +145,9 @@ max_temp: 120 pause_on_runout: True switch_pin: !PC13 -[heater_fan controller_fan] +[controller_fan controller_fan] pin: PA14 heater: heater_bed -heater_temp: 45.0 [heater_fan hotend_fan] pin: PA13 From 71604b712a0357b8bdea7cdd2eea18f45f7cec44 Mon Sep 17 00:00:00 2001 From: Ulf D <1coderookie@quantentunnel.de> Date: Fri, 8 Mar 2024 17:49:29 +0100 Subject: [PATCH 049/190] config: "static_value" in [output_pin enable_pin] is deprecated (#6520) Signed-off-by: Ulf Dieckmann <1coderookie@quantentunnel.de> --- config/printer-anycubic-kobra-go-2022.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/printer-anycubic-kobra-go-2022.cfg b/config/printer-anycubic-kobra-go-2022.cfg index d4fc409f0cfd..bb8a9267f68b 100644 --- a/config/printer-anycubic-kobra-go-2022.cfg +++ b/config/printer-anycubic-kobra-go-2022.cfg @@ -118,7 +118,7 @@ cycle_time: 0.00005 #20kHz [output_pin enable_pin] pin: PB6 -static_value: 1 +value: 1 #This pin enables the bed, hotend, extruder fan, part fan. [mcu] From 18de421c4acbdb00946df35dfb412dedb159abca Mon Sep 17 00:00:00 2001 From: Attila Date: Sun, 10 Mar 2024 15:48:48 -0700 Subject: [PATCH 050/190] stm32: Fix USART3 ALT pinout on STM32G0 (#6523) Signed-off-by: Attila Rakosi --- src/stm32/stm32f0_serial.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stm32/stm32f0_serial.c b/src/stm32/stm32f0_serial.c index e48960f1163c..b7f067b63fe0 100644 --- a/src/stm32/stm32f0_serial.c +++ b/src/stm32/stm32f0_serial.c @@ -68,7 +68,7 @@ DECL_CONSTANT_STR("RESERVE_PINS_serial", "PD9,PD8"); #define GPIO_Rx GPIO('D', 9) #define GPIO_Tx GPIO('D', 8) - #define USARTx_FUNCTION GPIO_FUNCTION(7) + #define USARTx_FUNCTION GPIO_FUNCTION(CONFIG_MACH_STM32G0 ? 0 : 7) #define USARTx USART3 #define USARTx_IRQn USART3_IRQn #elif CONFIG_STM32_SERIAL_UART4 From 0105aa330f2a4865b01cbbc548cba1322be5371e Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 3 Dec 2023 18:50:44 -0500 Subject: [PATCH 051/190] toolhead: Replace max_accel_to_decel with minimum_cruise_ratio The user facing max_accel_to_decel setting is complicated and confusing. Replace it with a new minimum_cruise_ratio parameter. Internally this user-facing parameter will calculate the existing low-level "accel_to_decel" mechanism. Signed-off-by: Kevin O'Connor --- config/printer-anycubic-4maxpro-2.0-2021.cfg | 1 - config/printer-sovol-sv05-2022.cfg | 2 +- docs/Config_Changes.md | 8 ++++ docs/Config_Reference.md | 25 ++++++++--- docs/G-Codes.md | 2 +- docs/Kinematics.md | 28 +++++++----- docs/Resonance_Compensation.md | 10 ++--- docs/Status_Reference.md | 2 +- klippy/extras/resonance_tester.py | 10 ++--- klippy/toolhead.py | 47 ++++++++++++-------- 10 files changed, 85 insertions(+), 50 deletions(-) diff --git a/config/printer-anycubic-4maxpro-2.0-2021.cfg b/config/printer-anycubic-4maxpro-2.0-2021.cfg index e0c83b6aeaa3..203e3c8f2f37 100644 --- a/config/printer-anycubic-4maxpro-2.0-2021.cfg +++ b/config/printer-anycubic-4maxpro-2.0-2021.cfg @@ -126,7 +126,6 @@ restart_method: arduino kinematics: cartesian max_velocity: 150 max_accel: 3000 -max_accel_to_decel: 1500 max_z_velocity: 7 max_z_accel: 50 square_corner_velocity: 5 diff --git a/config/printer-sovol-sv05-2022.cfg b/config/printer-sovol-sv05-2022.cfg index dafc6f3cf322..0ec03f84a186 100644 --- a/config/printer-sovol-sv05-2022.cfg +++ b/config/printer-sovol-sv05-2022.cfg @@ -19,7 +19,7 @@ restart_method: command kinematics: cartesian max_velocity: 300 max_accel: 1000 -max_accel_to_decel: 1000 +minimum_cruise_ratio: 0.0 max_z_velocity: 5 max_z_accel: 100 diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index 980c4f33ac7f..668732f994c5 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,14 @@ All dates in this document are approximate. ## Changes +20240313: The `max_accel_to_decel` parameter in the `[printer]` config +section has been deprecated. The `ACCEL_TO_DECEL` parameter of the +`SET_VELOCITY_LIMIT` command has been deprecated. The +`printer.toolhead.max_accel_to_decel` status has been removed. Use the +[minimum_cruise_ratio parameter](./Config_Reference.md#printer) +instead. The deprecated features will be removed in the near future, +and using them in the interim may result in subtly different behavior. + 20240215: Several deprecated features have been removed. Using "NTC 100K beta 3950" as a thermistor name has been removed (deprecated on 20211110). The `SYNC_STEPPER_TO_EXTRUDER` and diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 14b89200fb00..1b833eaf1e7f 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -97,13 +97,22 @@ max_accel: # will do so at the rate specified here. The value specified here # may be changed at runtime using the SET_VELOCITY_LIMIT command. # This parameter must be specified. -#max_accel_to_decel: -# A pseudo acceleration (in mm/s^2) controlling how fast the -# toolhead may go from acceleration to deceleration. It is used to -# reduce the top speed of short zig-zag moves (and thus reduce -# printer vibration from these moves). The value specified here may -# be changed at runtime using the SET_VELOCITY_LIMIT command. The -# default is half of max_accel. +#minimum_cruise_ratio: 0.5 +# Most moves will accelerate to a cruising speed, travel at that +# cruising speed, and then decelerate. However, some moves that +# travel a short distance could nominally accelerate and then +# immediately decelerate. This option reduces the top speed of these +# moves to ensure there is always a minimum distance traveled at a +# cruising speed. That is, it enforces a minimum distance traveled +# at cruising speed relative to the total distance traveled. It is +# intended to reduce the top speed of short zigzag moves (and thus +# reduce printer vibration from these moves). For example, a +# minimum_cruise_ratio of 0.5 would ensure that a standalone 1.5mm +# move would have a minimum cruising distance of 0.75mm. Specify a +# ratio of 0.0 to disable this feature (there would be no minimum +# cruising distance enforced between acceleration and deceleration). +# The value specified here may be changed at runtime using the +# SET_VELOCITY_LIMIT command. The default is 0.5. #square_corner_velocity: 5.0 # The maximum velocity (in mm/s) that the toolhead may travel a 90 # degree corner at. A non-zero value can reduce changes in extruder @@ -116,6 +125,8 @@ max_accel: # decelerate to zero at each corner. The value specified here may be # changed at runtime using the SET_VELOCITY_LIMIT command. The # default is 5mm/s. +#max_accel_to_decel: +# This parameter is deprecated and should no longer be used. ``` ### [stepper] diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 199dc283b7ce..1f88937aa7d9 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -1291,7 +1291,7 @@ The toolhead module is automatically loaded. #### SET_VELOCITY_LIMIT `SET_VELOCITY_LIMIT [VELOCITY=] [ACCEL=] -[ACCEL_TO_DECEL=] [SQUARE_CORNER_VELOCITY=]`: This +[MINIMUM_CRUISE_RATIO=] [SQUARE_CORNER_VELOCITY=]`: This command can alter the velocity limits that were specified in the printer config file. See the [printer config section](Config_Reference.md#printer) for a diff --git a/docs/Kinematics.md b/docs/Kinematics.md index 313d5dce035c..1996579d2315 100644 --- a/docs/Kinematics.md +++ b/docs/Kinematics.md @@ -96,7 +96,7 @@ Key formula for look-ahead: end_velocity^2 = start_velocity^2 + 2*accel*move_distance ``` -### Smoothed look-ahead +### Minimum cruise ratio Klipper also implements a mechanism for smoothing out the motions of short "zigzag" moves. Consider the following moves: @@ -105,21 +105,27 @@ short "zigzag" moves. Consider the following moves: In the above, the frequent changes from acceleration to deceleration can cause the machine to vibrate which causes stress on the machine -and increases the noise. To reduce this, Klipper tracks both regular -move acceleration as well as a virtual "acceleration to deceleration" -rate. Using this system, the top speed of these short "zigzag" moves -are limited to smooth out the printer motion: +and increases the noise. Klipper implements a mechanism to ensure +there is always some movement at a cruising speed between acceleration +and deceleration. This is done by reducing the top speed of some moves +(or sequence of moves) to ensure there is a minimum distance traveled +at cruising speed relative to the distance traveled during +acceleration and deceleration. + +Klipper implements this feature by tracking both a regular move +acceleration as well as a virtual "acceleration to deceleration" rate: ![smoothed](img/smoothed.svg.png) Specifically, the code calculates what the velocity of each move would be if it were limited to this virtual "acceleration to deceleration" -rate (half the normal acceleration rate by default). In the above -picture the dashed gray lines represent this virtual acceleration rate -for the first move. If a move can not reach its full cruising speed -using this virtual acceleration rate then its top speed is reduced to -the maximum speed it could obtain at this virtual acceleration -rate. For most moves the limit will be at or above the move's existing +rate. In the above picture the dashed gray lines represent this +virtual acceleration rate for the first move. If a move can not reach +its full cruising speed using this virtual acceleration rate then its +top speed is reduced to the maximum speed it could obtain at this +virtual acceleration rate. + +For most moves the limit will be at or above the move's existing limits and no change in behavior is induced. For short zigzag moves, however, this limit reduces the top speed. Note that it does not change the actual acceleration within the move - the move continues to diff --git a/docs/Resonance_Compensation.md b/docs/Resonance_Compensation.md index aaeaf7abf054..31f1b35e3b05 100644 --- a/docs/Resonance_Compensation.md +++ b/docs/Resonance_Compensation.md @@ -48,8 +48,8 @@ First, measure the **ringing frequency**. to 5.0. It is not advised to increase it when using input shaper because it can cause more smoothing in parts - it is better to use higher acceleration value instead. -2. Increase `max_accel_to_decel` by issuing the following command: - `SET_VELOCITY_LIMIT ACCEL_TO_DECEL=7000` +2. Disable the `miminum_cruise_ratio` feature by issuing the following + command: `SET_VELOCITY_LIMIT MINIMUM_CRUISE_RATIO=0` 3. Disable Pressure Advance: `SET_PRESSURE_ADVANCE ADVANCE=0` 4. If you have already added `[input_shaper]` section to the printer.cfg, execute `SET_INPUT_SHAPER SHAPER_FREQ_X=0 SHAPER_FREQ_Y=0` command. If you @@ -149,7 +149,7 @@ a few other related parameters. Print the ringing test model as follows: 1. Restart the firmware: `RESTART` -2. Prepare for test: `SET_VELOCITY_LIMIT ACCEL_TO_DECEL=7000` +2. Prepare for test: `SET_VELOCITY_LIMIT MINIMUM_CRUISE_RATIO=0` 3. Disable Pressure Advance: `SET_PRESSURE_ADVANCE ADVANCE=0` 4. Execute: `SET_INPUT_SHAPER SHAPER_TYPE=MZV` 5. Execute the command: @@ -270,7 +270,7 @@ frequencies after enabling [input_shaper], this section will not help with that. Assuming that you have sliced the ringing model with suggested parameters, complete the following steps for each of the axes X and Y: -1. Prepare for test: `SET_VELOCITY_LIMIT ACCEL_TO_DECEL=7000` +1. Prepare for test: `SET_VELOCITY_LIMIT MINIMUM_CRUISE_RATIO=0` 2. Make sure Pressure Advance is disabled: `SET_PRESSURE_ADVANCE ADVANCE=0` 3. Execute: `SET_INPUT_SHAPER SHAPER_TYPE=ZV` 4. From the existing ringing test model with your chosen input shaper select @@ -331,7 +331,7 @@ with suggested parameters, print the test model 3 times as follows. First time, prior to printing, run 1. `RESTART` -2. `SET_VELOCITY_LIMIT ACCEL_TO_DECEL=7000` +2. `SET_VELOCITY_LIMIT MINIMUM_CRUISE_RATIO=0` 3. `SET_PRESSURE_ADVANCE ADVANCE=0` 4. `SET_INPUT_SHAPER SHAPER_TYPE=2HUMP_EI SHAPER_FREQ_X=60 SHAPER_FREQ_Y=60` 5. `TUNING_TOWER COMMAND=SET_VELOCITY_LIMIT PARAMETER=ACCEL START=1500 STEP_DELTA=500 STEP_HEIGHT=5` diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index 055d1dc0eac0..0e72a12b1f9f 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -511,7 +511,7 @@ The following information is available in the `toolhead` object limit value (eg, `axis_minimum.x`, `axis_maximum.z`). - For Delta printers the `cone_start_z` is the max z height at maximum radius (`printer.toolhead.cone_start_z`). -- `max_velocity`, `max_accel`, `max_accel_to_decel`, +- `max_velocity`, `max_accel`, `minimum_cruise_ratio`, `square_corner_velocity`: The current printing limits that are in effect. This may differ from the config file settings if a `SET_VELOCITY_LIMIT` (or `M204`) command alters them at run-time. diff --git a/klippy/extras/resonance_tester.py b/klippy/extras/resonance_tester.py index d249412cadcc..fe8717d58545 100644 --- a/klippy/extras/resonance_tester.py +++ b/klippy/extras/resonance_tester.py @@ -77,11 +77,11 @@ def run_test(self, axis, gcmd): systime = self.printer.get_reactor().monotonic() toolhead_info = toolhead.get_status(systime) old_max_accel = toolhead_info['max_accel'] - old_max_accel_to_decel = toolhead_info['max_accel_to_decel'] + old_minimum_cruise_ratio = toolhead_info['minimum_cruise_ratio'] max_accel = self.freq_end * self.accel_per_hz self.gcode.run_script_from_command( - "SET_VELOCITY_LIMIT ACCEL=%.3f ACCEL_TO_DECEL=%.3f" % ( - max_accel, max_accel)) + "SET_VELOCITY_LIMIT ACCEL=%.3f MINIMUM_CRUISE_RATIO=0" + % (max_accel,)) input_shaper = self.printer.lookup_object('input_shaper', None) if input_shaper is not None and not gcmd.get_int('INPUT_SHAPING', 0): input_shaper.disable_shaping() @@ -108,8 +108,8 @@ def run_test(self, axis, gcmd): gcmd.respond_info("Testing frequency %.0f Hz" % (freq,)) # Restore the original acceleration values self.gcode.run_script_from_command( - "SET_VELOCITY_LIMIT ACCEL=%.3f ACCEL_TO_DECEL=%.3f" % ( - old_max_accel, old_max_accel_to_decel)) + "SET_VELOCITY_LIMIT ACCEL=%.3f MINIMUM_CRUISE_RATIO=%.3f" + % (old_max_accel, old_minimum_cruise_ratio)) # Restore input shaper if it was disabled for resonance testing if input_shaper is not None: input_shaper.enable_shaping() diff --git a/klippy/toolhead.py b/klippy/toolhead.py index ce014365c6f9..d386ec8e923b 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -217,12 +217,19 @@ def __init__(self, config): # Velocity and acceleration control self.max_velocity = config.getfloat('max_velocity', above=0.) self.max_accel = config.getfloat('max_accel', above=0.) - self.requested_accel_to_decel = config.getfloat( - 'max_accel_to_decel', self.max_accel * 0.5, above=0.) - self.max_accel_to_decel = self.requested_accel_to_decel + self.min_cruise_ratio = config.getfloat('minimum_cruise_ratio', None, + below=1., minval=0.) + if self.min_cruise_ratio is None: + self.min_cruise_ratio = 0.5 + req_accel_to_decel = config.getfloat('max_accel_to_decel', None, + above=0.) + if req_accel_to_decel is not None: + config.deprecate('max_accel_to_decel') + self.min_cruise_ratio = 1. - min(1., (req_accel_to_decel + / self.max_accel)) self.square_corner_velocity = config.getfloat( 'square_corner_velocity', 5., minval=0.) - self.junction_deviation = 0. + self.junction_deviation = self.max_accel_to_decel = 0. self._calc_junction_deviation() # Input stall detection self.check_stall_time = 0. @@ -561,7 +568,7 @@ def get_status(self, eventtime): 'position': self.Coord(*self.commanded_pos), 'max_velocity': self.max_velocity, 'max_accel': self.max_accel, - 'max_accel_to_decel': self.requested_accel_to_decel, + 'minimum_cruise_ratio': self.min_cruise_ratio, 'square_corner_velocity': self.square_corner_velocity}) return res def _handle_shutdown(self): @@ -600,8 +607,7 @@ def get_max_velocity(self): def _calc_junction_deviation(self): scv2 = self.square_corner_velocity**2 self.junction_deviation = scv2 * (math.sqrt(2.) - 1.) / self.max_accel - self.max_accel_to_decel = min(self.requested_accel_to_decel, - self.max_accel) + self.max_accel_to_decel = self.max_accel * (1. - self.min_cruise_ratio) def cmd_G4(self, gcmd): # Dwell delay = gcmd.get_float('P', 0., minval=0.) / 1000. @@ -615,29 +621,34 @@ def cmd_SET_VELOCITY_LIMIT(self, gcmd): max_accel = gcmd.get_float('ACCEL', None, above=0.) square_corner_velocity = gcmd.get_float( 'SQUARE_CORNER_VELOCITY', None, minval=0.) - requested_accel_to_decel = gcmd.get_float( - 'ACCEL_TO_DECEL', None, above=0.) + min_cruise_ratio = gcmd.get_float( + 'MINIMUM_CRUISE_RATIO', None, minval=0., below=1.) + if min_cruise_ratio is None: + req_accel_to_decel = gcmd.get_float('ACCEL_TO_DECEL', + None, above=0.) + if req_accel_to_decel is not None and max_accel is not None: + min_cruise_ratio = 1. - min(1., req_accel_to_decel / max_accel) + elif req_accel_to_decel is not None and max_accel is None: + min_cruise_ratio = 1. - min(1., (req_accel_to_decel + / self.max_accel)) if max_velocity is not None: self.max_velocity = max_velocity if max_accel is not None: self.max_accel = max_accel if square_corner_velocity is not None: self.square_corner_velocity = square_corner_velocity - if requested_accel_to_decel is not None: - self.requested_accel_to_decel = requested_accel_to_decel + if min_cruise_ratio is not None: + self.min_cruise_ratio = min_cruise_ratio self._calc_junction_deviation() msg = ("max_velocity: %.6f\n" "max_accel: %.6f\n" - "max_accel_to_decel: %.6f\n" + "minimum_cruise_ratio: %.6f\n" "square_corner_velocity: %.6f" % ( self.max_velocity, self.max_accel, - self.requested_accel_to_decel, - self.square_corner_velocity)) + self.min_cruise_ratio, self.square_corner_velocity)) self.printer.set_rollover_info("toolhead", "toolhead: %s" % (msg,)) - if (max_velocity is None and - max_accel is None and - square_corner_velocity is None and - requested_accel_to_decel is None): + if (max_velocity is None and max_accel is None + and square_corner_velocity is None and min_cruise_ratio is None): gcmd.respond_info(msg, log=False) def cmd_M204(self, gcmd): # Use S for accel From bddefdde36cab33b19df7e46824e922e9c045527 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 5 Mar 2024 19:24:03 -0500 Subject: [PATCH 052/190] pid_calibrate: Fix PID_CALIBRATE command when used with heater_generic Make sure the SAVE_CONFIG command saves the calculated PID parameters to the correct config name. Signed-off-by: Kevin O'Connor --- klippy/extras/heaters.py | 11 +++++++---- klippy/extras/pid_calibrate.py | 9 +++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/klippy/extras/heaters.py b/klippy/extras/heaters.py index 1c29aae81a16..6c8fb8224dbf 100644 --- a/klippy/extras/heaters.py +++ b/klippy/extras/heaters.py @@ -18,7 +18,8 @@ class Heater: def __init__(self, config, sensor): self.printer = config.get_printer() - self.name = config.get_name().split()[-1] + self.name = config.get_name() + self.short_name = short_name = self.name.split()[-1] # Setup sensor self.sensor = sensor self.min_temp = config.getfloat('min_temp', minval=KELVIN_TO_CELSIUS) @@ -55,11 +56,11 @@ def __init__(self, config, sensor): self.mcu_pwm.setup_cycle_time(pwm_cycle_time) self.mcu_pwm.setup_max_duration(MAX_HEAT_TIME) # Load additional modules - self.printer.load_object(config, "verify_heater %s" % (self.name,)) + self.printer.load_object(config, "verify_heater %s" % (short_name,)) self.printer.load_object(config, "pid_calibrate") gcode = self.printer.lookup_object("gcode") gcode.register_mux_command("SET_HEATER_TEMPERATURE", "HEATER", - self.name, self.cmd_SET_HEATER_TEMPERATURE, + short_name, self.cmd_SET_HEATER_TEMPERATURE, desc=self.cmd_SET_HEATER_TEMPERATURE_help) def set_pwm(self, read_time, value): if self.target_temp <= 0.: @@ -87,6 +88,8 @@ def temperature_callback(self, read_time, temp): self.can_extrude = (self.smoothed_temp >= self.min_extrude_temp) #logging.debug("temp: %.3f %f = %f", read_time, temp) # External commands + def get_name(self): + return self.name def get_pwm_delay(self): return self.pwm_delay def get_max_power(self): @@ -127,7 +130,7 @@ def stats(self, eventtime): last_pwm_value = self.last_pwm_value is_active = target_temp or last_temp > 50. return is_active, '%s: target=%.0f temp=%.1f pwm=%.3f' % ( - self.name, target_temp, last_temp, last_pwm_value) + self.short_name, target_temp, last_temp, last_pwm_value) def get_status(self, eventtime): with self.lock: target_temp = self.target_temp diff --git a/klippy/extras/pid_calibrate.py b/klippy/extras/pid_calibrate.py index f32f1be79856..20641167224d 100644 --- a/klippy/extras/pid_calibrate.py +++ b/klippy/extras/pid_calibrate.py @@ -43,11 +43,12 @@ def cmd_PID_CALIBRATE(self, gcmd): "The SAVE_CONFIG command will update the printer config file\n" "with these parameters and restart the printer." % (Kp, Ki, Kd)) # Store results for SAVE_CONFIG + cfgname = heater.get_name() configfile = self.printer.lookup_object('configfile') - configfile.set(heater_name, 'control', 'pid') - configfile.set(heater_name, 'pid_Kp', "%.3f" % (Kp,)) - configfile.set(heater_name, 'pid_Ki', "%.3f" % (Ki,)) - configfile.set(heater_name, 'pid_Kd', "%.3f" % (Kd,)) + configfile.set(cfgname, 'control', 'pid') + configfile.set(cfgname, 'pid_Kp', "%.3f" % (Kp,)) + configfile.set(cfgname, 'pid_Ki', "%.3f" % (Ki,)) + configfile.set(cfgname, 'pid_Kd', "%.3f" % (Kd,)) TUNE_PID_DELTA = 5.0 From bb512ef5d7cf746d4864cb4f4fbdcf91fbf7f832 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 5 Mar 2024 20:50:09 -0500 Subject: [PATCH 053/190] heaters: Clarify reported stats after a shutdown The pid logic can continue after a shutdown, even though the pin commands sent to the mcu are ignored. However, this behavior can result in confusing "stats" messages in the log. Explicitly disable updates after a shutdown event so that the log statistics are more clear. Signed-off-by: Kevin O'Connor --- klippy/extras/heaters.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/klippy/extras/heaters.py b/klippy/extras/heaters.py index 6c8fb8224dbf..5480501326b4 100644 --- a/klippy/extras/heaters.py +++ b/klippy/extras/heaters.py @@ -37,6 +37,7 @@ def __init__(self, config, sensor): self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.) self.smooth_time = config.getfloat('smooth_time', 1., above=0.) self.inv_smooth_time = 1. / self.smooth_time + self.is_shutdown = False self.lock = threading.Lock() self.last_temp = self.smoothed_temp = self.target_temp = 0. self.last_temp_time = 0. @@ -62,8 +63,10 @@ def __init__(self, config, sensor): gcode.register_mux_command("SET_HEATER_TEMPERATURE", "HEATER", short_name, self.cmd_SET_HEATER_TEMPERATURE, desc=self.cmd_SET_HEATER_TEMPERATURE_help) + self.printer.register_event_handler("klippy:shutdown", + self._handle_shutdown) def set_pwm(self, read_time, value): - if self.target_temp <= 0.: + if self.target_temp <= 0. or self.is_shutdown: value = 0. if ((read_time < self.next_pwm_time or not self.last_pwm_value) and abs(value - self.last_pwm_value) < 0.05): @@ -87,6 +90,8 @@ def temperature_callback(self, read_time, temp): self.smoothed_temp += temp_diff * adj_time 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 # External commands def get_name(self): return self.name From d99d1a84631fb3840132c492bc32fd9579740d1e Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 5 Mar 2024 21:32:36 -0500 Subject: [PATCH 054/190] mcu: Write a warning to the log if an incorrect mcu frequency is detected Signed-off-by: Kevin O'Connor --- klippy/mcu.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/klippy/mcu.py b/klippy/mcu.py index f9b547c94479..afd0d75d8882 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -588,6 +588,7 @@ def __init__(self, config, clocksync): printer.register_event_handler("klippy:connect", self._connect) printer.register_event_handler("klippy:shutdown", self._shutdown) printer.register_event_handler("klippy:disconnect", self._disconnect) + printer.register_event_handler("klippy:ready", self._ready) # Serial callbacks def _handle_mcu_stats(self, params): count = params['count'] @@ -799,6 +800,19 @@ def _mcu_identify(self): self.register_response(self._handle_shutdown, 'shutdown') self.register_response(self._handle_shutdown, 'is_shutdown') self.register_response(self._handle_mcu_stats, 'stats') + def _ready(self): + if self.is_fileoutput(): + return + # Check that reported mcu frequency is in range + mcu_freq = self._mcu_freq + systime = self._reactor.monotonic() + get_clock = self._clocksync.get_clock + calc_freq = get_clock(systime + 1) - get_clock(systime) + mcu_freq_mhz = int(mcu_freq / 1000000. + 0.5) + calc_freq_mhz = int(calc_freq / 1000000. + 0.5) + if mcu_freq_mhz != calc_freq_mhz: + logging.warn("MCU '%s' configured for %dMhz but running at %dMhz!", + self._name, mcu_freq_mhz, calc_freq_mhz) # Config creation helpers def setup_pin(self, pin_type, pin_params): pcs = {'endstop': MCU_endstop, From 0291a1554cb7bb8c74db15591150aa7958905df5 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 5 Mar 2024 21:45:16 -0500 Subject: [PATCH 055/190] configfile: Add support for reporting runtime_warnings via the API server Add a new runtime_warning() method that will add a 'runtime_warning' type message to the printer.configfile.warnings object. Signed-off-by: Kevin O'Connor --- klippy/configfile.py | 12 ++++++++++-- klippy/mcu.py | 6 ++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/klippy/configfile.py b/klippy/configfile.py index f099b563406d..b1f7d6d69e75 100644 --- a/klippy/configfile.py +++ b/klippy/configfile.py @@ -143,6 +143,8 @@ def __init__(self, printer): self.printer = printer self.autosave = None self.deprecated = {} + self.runtime_warnings = [] + self.deprecate_warnings = [] self.status_raw_config = {} self.status_save_pending = {} self.status_settings = {} @@ -314,6 +316,11 @@ def log_config(self, config): "======================="] self.printer.set_rollover_info("config", "\n".join(lines)) # Status reporting + def runtime_warning(self, msg): + logging.warn(msg) + res = {'type': 'runtime_warning', 'message': msg} + self.runtime_warnings.append(res) + self.status_warnings = self.runtime_warnings + self.deprecate_warnings def deprecate(self, section, option, value=None, msg=None): self.deprecated[(section, option, value)] = msg def _build_status(self, config): @@ -325,7 +332,7 @@ def _build_status(self, config): self.status_settings = {} for (section, option), value in config.access_tracking.items(): self.status_settings.setdefault(section, {})[option] = value - self.status_warnings = [] + self.deprecate_warnings = [] for (section, option, value), msg in self.deprecated.items(): if value is None: res = {'type': 'deprecated_option'} @@ -334,7 +341,8 @@ def _build_status(self, config): res['message'] = msg res['section'] = section res['option'] = option - self.status_warnings.append(res) + self.deprecate_warnings.append(res) + self.status_warnings = self.runtime_warnings + self.deprecate_warnings def get_status(self, eventtime): return {'config': self.status_raw_config, 'settings': self.status_settings, diff --git a/klippy/mcu.py b/klippy/mcu.py index afd0d75d8882..366f78062775 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -811,8 +811,10 @@ def _ready(self): mcu_freq_mhz = int(mcu_freq / 1000000. + 0.5) calc_freq_mhz = int(calc_freq / 1000000. + 0.5) if mcu_freq_mhz != calc_freq_mhz: - logging.warn("MCU '%s' configured for %dMhz but running at %dMhz!", - self._name, mcu_freq_mhz, calc_freq_mhz) + pconfig = self._printer.lookup_object('configfile') + msg = ("MCU '%s' configured for %dMhz but running at %dMhz!" + % (self._name, mcu_freq_mhz, calc_freq_mhz)) + pconfig.runtime_warning(msg) # Config creation helpers def setup_pin(self, pin_type, pin_params): pcs = {'endstop': MCU_endstop, From 09a78c31bbb3d6959276e50283a700aafe6b9000 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 7 Mar 2024 19:32:28 -0500 Subject: [PATCH 056/190] buildcommands: Add Klipper app name and license to mcu data dictionary Add the Klipper name and license to the mcu data dictionary so that it can be found in the flash. Signed-off-by: Kevin O'Connor --- scripts/buildcommands.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/buildcommands.py b/scripts/buildcommands.py index 2acf3e84699d..236373c2f078 100644 --- a/scripts/buildcommands.py +++ b/scripts/buildcommands.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 # Script to handle build time requests embedded in C code. # -# Copyright (C) 2016-2021 Kevin O'Connor +# Copyright (C) 2016-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import sys, os, subprocess, optparse, logging, shlex, socket, time, traceback @@ -518,6 +518,8 @@ def __init__(self): def update_data_dictionary(self, data): data['version'] = self.version data['build_versions'] = self.toolstr + data['app'] = 'Klipper' + data['license'] = 'GNU GPLv3' def generate_code(self, options): cleanbuild, self.toolstr = tool_versions(options.tools) self.version = build_version(options.extra, cleanbuild) From bfb71bc2dc63f2911a11ebf580f82b1e8b2706c4 Mon Sep 17 00:00:00 2001 From: BIGTREETECH <38851044+bigtreetech@users.noreply.github.com> Date: Fri, 15 Mar 2024 22:12:05 +0800 Subject: [PATCH 057/190] stm32: Add i2c3_PC0_PC1 for stm32g0 (#6529) Signed-off-by: Alan.Ma from BigTreeTech --- src/stm32/stm32f0_i2c.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/stm32/stm32f0_i2c.c b/src/stm32/stm32f0_i2c.c index e9cadccbdfad..21e65488eda0 100644 --- a/src/stm32/stm32f0_i2c.c +++ b/src/stm32/stm32f0_i2c.c @@ -44,6 +44,8 @@ struct i2c_info { #ifdef I2C3 DECL_ENUMERATION("i2c_bus", "i2c3_PB3_PB4", 5); DECL_CONSTANT_STR("BUS_PINS_i2c3_PB3_PB4", "PB3,PB4"); + DECL_ENUMERATION("i2c_bus", "i2c3_PC0_PC1", 6); + DECL_CONSTANT_STR("BUS_PINS_i2c3_PC0_PC1", "PC0,PC1"); #endif #elif CONFIG_MACH_STM32L4 DECL_ENUMERATION("i2c_bus", "i2c1_PB6_PB7", 0); @@ -99,6 +101,7 @@ static const struct i2c_info i2c_bus[] = { { I2C2, GPIO('B', 13), GPIO('B', 14), GPIO_FUNCTION(6) }, #ifdef I2C3 { I2C3, GPIO('B', 3), GPIO('B', 4), GPIO_FUNCTION(6) }, + { I2C3, GPIO('C', 0), GPIO('C', 1), GPIO_FUNCTION(6) }, #endif #elif CONFIG_MACH_STM32L4 { I2C1, GPIO('B', 6), GPIO('B', 7), GPIO_FUNCTION(4) }, From 78a15b6d814f96dbdef6751478ff2bfbb62be046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=BCffner?= Date: Fri, 15 Mar 2024 16:29:35 +0100 Subject: [PATCH 058/190] scripts: use greenlet version depending on python version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Markus Küffner --- scripts/klippy-requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/klippy-requirements.txt b/scripts/klippy-requirements.txt index 52a3092350d4..7fb2354040e9 100644 --- a/scripts/klippy-requirements.txt +++ b/scripts/klippy-requirements.txt @@ -4,7 +4,8 @@ # pip install -r klippy-requirements.txt cffi==1.14.6 pyserial==3.4 -greenlet==2.0.2 +greenlet==2.0.2 ; python_version < '3.12' +greenlet==3.0.3 ; python_version >= '3.12' Jinja2==2.11.3 python-can==3.3.4 markupsafe==1.1.1 From 235b75be3c287a9fdcde54b347734bf6a8de2ade Mon Sep 17 00:00:00 2001 From: Mad Beggar Date: Tue, 19 Mar 2024 16:16:42 -0400 Subject: [PATCH 059/190] hc32f460: Adding support for 100pin version of H32F460 (#6488) Signed-off-by: Guillaume Giraudon --- src/hc32f460/gpio.c | 3 ++- src/hc32f460/hard_pwm.c | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/hc32f460/gpio.c b/src/hc32f460/gpio.c index e3b98df443f2..a1144927005a 100644 --- a/src/hc32f460/gpio.c +++ b/src/hc32f460/gpio.c @@ -18,7 +18,8 @@ DECL_ENUMERATION_RANGE("pin", "PA0", GPIO('A', 0), 16); DECL_ENUMERATION_RANGE("pin", "PB0", GPIO('B', 0), 16); DECL_ENUMERATION_RANGE("pin", "PC0", GPIO('C', 0), 16); -DECL_ENUMERATION_RANGE("pin", "PD2", GPIO('D', 2), 1); +DECL_ENUMERATION_RANGE("pin", "PD0", GPIO('D', 0), 16); +DECL_ENUMERATION_RANGE("pin", "PE0", GPIO('E', 0), 16); DECL_ENUMERATION_RANGE("pin", "PH2", PortH * 16 + 2, 1); // H: special case diff --git a/src/hc32f460/hard_pwm.c b/src/hc32f460/hard_pwm.c index 9d3387e604d6..8cf57acf7ed7 100644 --- a/src/hc32f460/hard_pwm.c +++ b/src/hc32f460/hard_pwm.c @@ -75,6 +75,22 @@ static const struct gpio_pwm_info pwm_mapping[] = { {GPIO('C',14), 4, TimeraCh5}, {GPIO('C',15), 4, TimeraCh6}, {GPIO('D', 2), 2, TimeraCh4}, + {GPIO('D',12), 4, TimeraCh1}, + {GPIO('D',13), 4, TimeraCh2}, + {GPIO('D',14), 4, TimeraCh3}, + {GPIO('D',15), 4, TimeraCh4}, + {GPIO('E', 2), 3, TimeraCh5}, + {GPIO('E', 3), 3, TimeraCh6}, + {GPIO('E', 4), 3, TimeraCh7}, + {GPIO('E', 5), 3, TimeraCh8}, + {GPIO('E', 8), 1, TimeraCh5}, + {GPIO('E', 9), 1, TimeraCh1}, + {GPIO('E',10), 1, TimeraCh6}, + {GPIO('E',11), 1, TimeraCh2}, + {GPIO('E',12), 1, TimeraCh7}, + {GPIO('E',13), 1, TimeraCh3}, + {GPIO('E',14), 1, TimeraCh4}, + {GPIO('E',15), 1, TimeraCh8}, }; From e6df93fcf88f378097fc3a0b8ca6363c21a91128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Doma=C5=84ski?= Date: Wed, 21 Feb 2024 00:23:57 +0100 Subject: [PATCH 060/190] tmc2240: add ADC voltage formatters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kamil DomaÅ„ski --- klippy/extras/tmc2240.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/klippy/extras/tmc2240.py b/klippy/extras/tmc2240.py index aef2280f279a..14d5dd9188be 100644 --- a/klippy/extras/tmc2240.py +++ b/klippy/extras/tmc2240.py @@ -259,6 +259,8 @@ "s2vsa": (lambda v: "1(ShortToSupply_A!)" if v else ""), "s2vsb": (lambda v: "1(ShortToSupply_B!)" if v else ""), "adc_temp": (lambda v: "0x%04x(%.1fC)" % (v, ((v - 2038) / 7.7))), + "adc_vsupply": (lambda v: "0x%04x(%.3fV)" % (v, v * 0.009732)), + "adc_ain": (lambda v: "0x%04x(%.3fmV)" % (v, v * 0.3052)), }) From de1cf216acf1c2fc15b298d4c052a483189b0ed9 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 7 Jan 2024 23:34:59 -0500 Subject: [PATCH 061/190] docs: Sort axis_twist_compensation in G-Codes.md Signed-off-by: Kevin O'Connor --- docs/G-Codes.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 1f88937aa7d9..0b8aa6e501e1 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -139,6 +139,17 @@ Writes raw "value" into register "register". Both "value" and and refer to sensor data sheet for the reference. This is only available for tle5012b chips. +### [axis_twist_compensation] + +The following commands are available when the +[axis_twist_compensation config +section](Config_Reference.md#axis_twist_compensation) is enabled. + +#### AXIS_TWIST_COMPENSATION_CALIBRATE +`AXIS_TWIST_COMPENSATION_CALIBRATE [SAMPLE_COUNT=]`: Initiates the X +twist calibration wizard. `SAMPLE_COUNT` specifies the number of points along +the X axis to calibrate at and defaults to 3. + ### [bed_mesh] The following commands are available when the @@ -1353,17 +1364,6 @@ print. #### SDCARD_RESET_FILE `SDCARD_RESET_FILE`: Unload file and clear SD state. -### [axis_twist_compensation] - -The following commands are available when the -[axis_twist_compensation config -section](Config_Reference.md#axis_twist_compensation) is enabled. - -#### AXIS_TWIST_COMPENSATION_CALIBRATE -`AXIS_TWIST_COMPENSATION_CALIBRATE [SAMPLE_COUNT=]`: Initiates the X -twist calibration wizard. `SAMPLE_COUNT` specifies the number of points along -the X axis to calibrate at and defaults to 3. - ### [z_thermal_adjust] The following commands are available when the From 40728e923132b267dde00db187e3d110db4e1075 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 15 Dec 2023 18:04:17 -0500 Subject: [PATCH 062/190] motan: Support recording lis2dw and mpu9250 sensors from data_logger.py Signed-off-by: Kevin O'Connor --- scripts/motan/data_logger.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/scripts/motan/data_logger.py b/scripts/motan/data_logger.py index 7d704c83cec6..81370713cbf9 100755 --- a/scripts/motan/data_logger.py +++ b/scripts/motan/data_logger.py @@ -151,16 +151,15 @@ def handle_subscribe(self, msg, raw_msg): self.send_subscribe("stepq:" + stepper, "motion_report/dump_stepper", {"name": stepper}) # Subscribe to additional sensor data + stypes = ["adxl345", "lis2dw", "mpu9250", "angle"] config = status["configfile"]["settings"] for cfgname in config.keys(): - if cfgname == "adxl345" or cfgname.startswith("adxl345 "): - aname = cfgname.split()[-1] - self.send_subscribe("adxl345:" + aname, "adxl345/dump_adxl345", - {"sensor": aname}) - if cfgname.startswith("angle "): - aname = cfgname.split()[1] - self.send_subscribe("angle:" + aname, "angle/dump_angle", - {"sensor": aname}) + for st in stypes: + if cfgname == st or cfgname.startswith(st + " "): + aname = cfgname.split()[-1] + lname = "%s:%s" % (st, aname) + qcmd = "%s/dump_%s" % (st, st) + self.send_subscribe(lname, qcmd, {"sensor": aname}) def handle_dump(self, msg, raw_msg): msg_id = msg["id"] if "result" not in msg: From d9043345b615a4b64333a006d9f1fd40f386a5e4 Mon Sep 17 00:00:00 2001 From: Carl Richard Theodor Schneider Date: Wed, 13 Mar 2024 22:24:05 +0100 Subject: [PATCH 063/190] linux: Allow for more i2c buses Similar to commit df79893, this allows klipper to use up to /dev/i2c-14. Similar to before, this limit is arbitrary. This is required for some other SoCs, which have even more i2c buses available, e.g. the rk3399: $ ls -1 /dev/i2c-* /dev/i2c-0 /dev/i2c-3 /dev/i2c-7 Signed-off-by: Carl Richard Theodor Schneider --- src/linux/i2c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linux/i2c.c b/src/linux/i2c.c index 2994403f2161..b328dc56eaa6 100644 --- a/src/linux/i2c.c +++ b/src/linux/i2c.c @@ -14,7 +14,7 @@ #include "internal.h" // report_errno #include "sched.h" // sched_shutdown -DECL_ENUMERATION_RANGE("i2c_bus", "i2c.0", 0, 7); +DECL_ENUMERATION_RANGE("i2c_bus", "i2c.0", 0, 15); struct i2c_s { uint32_t bus; From e37b007f67e5bdc330af45b78643f7789c789907 Mon Sep 17 00:00:00 2001 From: TheParanoidEngineer <158858389+TheParanoidEngineer@users.noreply.github.com> Date: Mon, 25 Mar 2024 13:05:22 -0400 Subject: [PATCH 064/190] docs: Update Measuring_Resonances.md (#6515) Changed "libopenblas-base" to "libopenblas-dev" Signed-off-by: Philip Weber --- docs/Measuring_Resonances.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Measuring_Resonances.md b/docs/Measuring_Resonances.md index d1d998941dac..c06f17a582aa 100644 --- a/docs/Measuring_Resonances.md +++ b/docs/Measuring_Resonances.md @@ -207,7 +207,7 @@ software dependencies not installed by default. First, run on your Raspberry Pi the following commands: ``` sudo apt update -sudo apt install python3-numpy python3-matplotlib libatlas-base-dev libopenblas-base +sudo apt install python3-numpy python3-matplotlib libatlas-base-dev libopenblas-dev ``` Next, in order to install NumPy in the Klipper environment, run the command: From 239f8e59e0340a87ffa82ad80968fa06ea87bc3d Mon Sep 17 00:00:00 2001 From: Mathias Pihl Date: Wed, 3 Apr 2024 02:57:19 +0200 Subject: [PATCH 065/190] scripts: Mark install-ubuntu-22.04 as executable (#6505) Signed-off-by: Mathias Pihl --- scripts/install-ubuntu-22.04.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/install-ubuntu-22.04.sh diff --git a/scripts/install-ubuntu-22.04.sh b/scripts/install-ubuntu-22.04.sh old mode 100644 new mode 100755 From bedec55154771d5b02075c25be1a6d1f4bf91a07 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 20 Mar 2024 20:02:27 -0400 Subject: [PATCH 066/190] motion_report: Don't negate step_distance on steppers with inverted dir pin When querying the stepper motion queue, the resulting "interval", "count", and "add" are already normalized to the correct direction. That is, the "count" field will be positive if moving in a positive axis direction and negative if moving in the reverse direction. So, negating the step_distance field just complicates the readers. Signed-off-by: Kevin O'Connor --- klippy/extras/motion_report.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/klippy/extras/motion_report.py b/klippy/extras/motion_report.py index 25c1d5e6c604..c142fb39346b 100644 --- a/klippy/extras/motion_report.py +++ b/klippy/extras/motion_report.py @@ -56,8 +56,6 @@ def _process_batch(self, eventtime): mcu_pos = first.start_position start_position = self.mcu_stepper.mcu_to_commanded_position(mcu_pos) step_dist = self.mcu_stepper.get_step_dist() - if self.mcu_stepper.get_dir_inverted()[0]: - step_dist = -step_dist d = [(s.interval, s.step_count, s.add) for s in data] return {"data": d, "start_position": start_position, "start_mcu_position": mcu_pos, "step_distance": step_dist, From 0aacbc39736c933491690bf8174a0658acf4482f Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 21 Mar 2024 22:00:32 -0400 Subject: [PATCH 067/190] toolhead: Populate minimum_cruise_ratio to printer.configfile.settings The default minimum_cruise_ratio setting does not get populated to the printer.configfile.settings information due to the way the max_accel_to_decel backwards compatibility support was implemented. Slightly rework the config reading so that the default for minimum_cruise_ratio is populated there. Reported by @ReXT3D. Signed-off-by: Kevin O'Connor --- klippy/toolhead.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index d386ec8e923b..a995e4b1e2dd 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -217,16 +217,17 @@ def __init__(self, config): # Velocity and acceleration control self.max_velocity = config.getfloat('max_velocity', above=0.) self.max_accel = config.getfloat('max_accel', above=0.) - self.min_cruise_ratio = config.getfloat('minimum_cruise_ratio', None, - below=1., minval=0.) - if self.min_cruise_ratio is None: - self.min_cruise_ratio = 0.5 + min_cruise_ratio = 0.5 + if config.getfloat('minimum_cruise_ratio', None) is None: req_accel_to_decel = config.getfloat('max_accel_to_decel', None, above=0.) if req_accel_to_decel is not None: config.deprecate('max_accel_to_decel') - self.min_cruise_ratio = 1. - min(1., (req_accel_to_decel - / self.max_accel)) + min_cruise_ratio = 1. - min(1., (req_accel_to_decel + / self.max_accel)) + self.min_cruise_ratio = config.getfloat('minimum_cruise_ratio', + min_cruise_ratio, + below=1., minval=0.) self.square_corner_velocity = config.getfloat( 'square_corner_velocity', 5., minval=0.) self.junction_deviation = self.max_accel_to_decel = 0. From 9e1cbdcee3defd9ce173316a96074de5b13e7a74 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 21 Mar 2024 22:10:27 -0400 Subject: [PATCH 068/190] virtual_sdcard: Fix handling of unicode characters on Python2 Commit 600e89ae fixed unicode handling on Python3, but broke Python2 support. Use an alternate implementation that should work for both Python3 and Python2. Signed-off-by: Kevin O'Connor --- klippy/extras/virtual_sdcard.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/klippy/extras/virtual_sdcard.py b/klippy/extras/virtual_sdcard.py index 1bb914ab22c4..d49ebbcc41f4 100644 --- a/klippy/extras/virtual_sdcard.py +++ b/klippy/extras/virtual_sdcard.py @@ -1,9 +1,9 @@ # Virtual sdcard support (print files directly from a host g-code file) # -# Copyright (C) 2018 Kevin O'Connor +# Copyright (C) 2018-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import os, logging, io +import os, sys, logging, io VALID_GCODE_EXTS = ['gcode', 'g', 'gco'] @@ -258,7 +258,10 @@ def work_handler(self, eventtime): # Dispatch command self.cmd_from_sd = True line = lines.pop() - next_file_position = self.file_position + len(line.encode()) + 1 + if sys.version_info.major >= 3: + next_file_position = self.file_position + len(line.encode()) + 1 + else: + next_file_position = self.file_position + len(line) + 1 self.next_file_position = next_file_position try: self.gcode.run_script(line) From 67c152745e573afe82916bc40892dd1f6ac1b40a Mon Sep 17 00:00:00 2001 From: FOG_Yamato Date: Wed, 3 Apr 2024 05:02:22 +0300 Subject: [PATCH 069/190] stm32: Add i2c3 bus to STM32H7 (#6541) Signed-off-by: Balanuta Simion --- src/stm32/stm32f0_i2c.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/stm32/stm32f0_i2c.c b/src/stm32/stm32f0_i2c.c index 21e65488eda0..1441079f73b8 100644 --- a/src/stm32/stm32f0_i2c.c +++ b/src/stm32/stm32f0_i2c.c @@ -84,6 +84,8 @@ struct i2c_info { DECL_CONSTANT_STR("BUS_PINS_i2c1_PB8_PB9", "PB8,PB9"); DECL_ENUMERATION("i2c_bus", "i2c2_PB10_PB11", 2); DECL_CONSTANT_STR("BUS_PINS_i2c2_PB10_PB11", "PB10,PB11"); + DECL_ENUMERATION("i2c_bus", "i2c3_PA8_PC9", 3); + DECL_CONSTANT_STR("BUS_PINS_i2c3_PA8_PC9", "PA8,PC9"); #endif static const struct i2c_info i2c_bus[] = { @@ -123,6 +125,7 @@ static const struct i2c_info i2c_bus[] = { { I2C1, GPIO('B', 6), GPIO('B', 7), GPIO_FUNCTION(4) }, { I2C1, GPIO('B', 8), GPIO('B', 9), GPIO_FUNCTION(4) }, { I2C2, GPIO('B', 10), GPIO('B', 11), GPIO_FUNCTION(4) }, + { I2C3, GPIO('A', 8), GPIO('C', 9), GPIO_FUNCTION(4) }, #endif }; From 5e280680c54efb594f312aacea585851d4956d66 Mon Sep 17 00:00:00 2001 From: John Unland Date: Tue, 2 Apr 2024 20:08:35 -0600 Subject: [PATCH 070/190] makefile: fix warning about lto serial compilation (#6543) Signed-off-by: John Unland --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 106157265478..6d0184037d8a 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ cc-option=$(shell if test -z "`$(1) $(2) -S -o /dev/null -xc /dev/null 2>&1`" \ CFLAGS := -I$(OUT) -Isrc -I$(OUT)board-generic/ -std=gnu11 -O2 -MD \ -Wall -Wold-style-definition $(call cc-option,$(CC),-Wtype-limits,) \ -ffunction-sections -fdata-sections -fno-delete-null-pointer-checks -CFLAGS += -flto -fwhole-program -fno-use-linker-plugin -ggdb3 +CFLAGS += -flto=auto -fwhole-program -fno-use-linker-plugin -ggdb3 OBJS_klipper.elf = $(patsubst %.c, $(OUT)src/%.o,$(src-y)) OBJS_klipper.elf += $(OUT)compile_time_request.o From 75a40e817db4d5bc9d7d893fad499907d2b910a2 Mon Sep 17 00:00:00 2001 From: Robert Cambridge Date: Fri, 29 Mar 2024 18:35:16 +0100 Subject: [PATCH 071/190] stm32: fix support for USARTs on STM32G0B0 Signed-off-by: Robert Cambridge --- src/stm32/stm32f0_serial.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/stm32/stm32f0_serial.c b/src/stm32/stm32f0_serial.c index b7f067b63fe0..c987f149e561 100644 --- a/src/stm32/stm32f0_serial.c +++ b/src/stm32/stm32f0_serial.c @@ -102,6 +102,13 @@ #define USART5_IRQn USART3_4_5_6_LPUART1_IRQn #define USART6_IRQn USART3_4_5_6_LPUART1_IRQn #endif + #if CONFIG_MACH_STM32G0B0 + #define USART2_IRQn USART2_IRQn + #define USART3_IRQn USART3_4_5_6_IRQn + #define USART4_IRQn USART3_4_5_6_IRQn + #define USART5_IRQn USART3_4_5_6_IRQn + #define USART6_IRQn USART3_4_5_6_IRQn + #endif #define USART_CR1_RXNEIE USART_CR1_RXNEIE_RXFNEIE #define USART_CR1_TXEIE USART_CR1_TXEIE_TXFNFIE #define USART_ISR_RXNE USART_ISR_RXNE_RXFNE From 24c884e9f393fa42c37e38d12d3cedb1adb23776 Mon Sep 17 00:00:00 2001 From: Michael 'ASAP' Weinrich Date: Thu, 15 Feb 2024 20:11:47 -0800 Subject: [PATCH 072/190] makefile: Replace CFLAGS -I with -iquote The -iquote tells GCC to only search that path when resolving a quoted "include" (vs ) which by convention imples a include from the projects own soruce tree. This prevents a conflict between Klippers "sched.h" and "gpio.h" and and glibc . Signed-off-by: Michael 'ASAP' Weinrich --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 6d0184037d8a..735d18633272 100644 --- a/Makefile +++ b/Makefile @@ -29,8 +29,9 @@ dirs-y = src cc-option=$(shell if test -z "`$(1) $(2) -S -o /dev/null -xc /dev/null 2>&1`" \ ; then echo "$(2)"; else echo "$(3)"; fi ;) -CFLAGS := -I$(OUT) -Isrc -I$(OUT)board-generic/ -std=gnu11 -O2 -MD \ - -Wall -Wold-style-definition $(call cc-option,$(CC),-Wtype-limits,) \ +CFLAGS := -iquote $(OUT) -iquote src -iquote $(OUT)board-generic/ \ + -std=gnu11 -O2 -MD -Wall \ + -Wold-style-definition $(call cc-option,$(CC),-Wtype-limits,) \ -ffunction-sections -fdata-sections -fno-delete-null-pointer-checks CFLAGS += -flto=auto -fwhole-program -fno-use-linker-plugin -ggdb3 From 6f16e111977d0aa68fb4c6362509cec07a94115c Mon Sep 17 00:00:00 2001 From: Michael 'ASAP' Weinrich Date: Thu, 15 Feb 2024 20:17:08 -0800 Subject: [PATCH 073/190] linux: Don't use absolute paths for include Not all systems (i.e. Nix) repect the standard Linux filesystem hierarchy, instead relative paths should be used and allowing GCC to rely on it's builtin search paths. Signed-off-by: Michael 'ASAP' Weinrich --- src/linux/gpio.c | 2 +- src/linux/main.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/linux/gpio.c b/src/linux/gpio.c index bb07f5a07a9f..c7f4c5bf66c2 100644 --- a/src/linux/gpio.c +++ b/src/linux/gpio.c @@ -10,7 +10,7 @@ #include // memset #include // ioctl #include // close -#include // GPIOHANDLE_REQUEST_OUTPUT +#include // GPIOHANDLE_REQUEST_OUTPUT #include "command.h" // shutdown #include "gpio.h" // gpio_out_write #include "internal.h" // report_errno diff --git a/src/linux/main.c b/src/linux/main.c index f9ea3f6daaa8..b260f162b800 100644 --- a/src/linux/main.c +++ b/src/linux/main.c @@ -4,7 +4,7 @@ // // This file may be distributed under the terms of the GNU GPLv3 license. -#include // sched_setscheduler sched_get_priority_max +#include // sched_setscheduler sched_get_priority_max #include // fprintf #include // memset #include // getopt From b029d0466841b90b54279500f70a92deacfd6c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viesturs=20Zari=C5=86=C5=A1?= Date: Thu, 4 Apr 2024 22:46:30 +0200 Subject: [PATCH 074/190] manual_stepper: Add basic status. (#6527) Adding position and enabled in manual_stepper status. Enabled is already available through stepper_enable object. But this makes it more straightforward to access it. Signed-off-by: Viesturs Zarins --- docs/Status_Reference.md | 7 +++++++ klippy/extras/manual_stepper.py | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index 0e72a12b1f9f..80f53d9f3dab 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -293,6 +293,13 @@ understands it). - `z_position_lower`: Last probe attempt just lower than the current height. - `z_position_upper`: Last probe attempt just greater than the current height. +## manual_stepper + +The following information is available in the +`manual_stepper` object: +- `enabled`: Returns True if the stepper is currently enabled. +- `position`: The requested position. + ## mcu The following information is available in diff --git a/klippy/extras/manual_stepper.py b/klippy/extras/manual_stepper.py index 40db4a50316e..e18989d3a6c5 100644 --- a/klippy/extras/manual_stepper.py +++ b/klippy/extras/manual_stepper.py @@ -104,6 +104,11 @@ def cmd_MANUAL_STEPPER(self, gcmd): self.do_move(movepos, speed, accel, sync) elif gcmd.get_int('SYNC', 0): self.sync_print_time() + + def get_status(self, eventtime): + return {'position': self.rail.get_commanded_position(), + 'enabled': self.steppers[0].is_motor_enabled()} + # Toolhead wrappers to support homing def flush_step_generation(self): self.sync_print_time() From 01c7befacb5b71643130433f2afb3f22ece68d71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Doma=C5=84ski?= Date: Fri, 5 Apr 2024 23:43:43 +0200 Subject: [PATCH 075/190] klippy: remove a few unused variable assignments (#6504) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kamil DomaÅ„ski --- klippy/configfile.py | 1 - klippy/extras/dotstar.py | 1 - klippy/extras/endstop_phase.py | 1 - klippy/extras/idle_timeout.py | 2 +- klippy/extras/input_shaper.py | 1 - klippy/kinematics/hybrid_corexy.py | 1 - klippy/kinematics/hybrid_corexz.py | 1 - klippy/mcu.py | 1 - klippy/toolhead.py | 1 - 9 files changed, 1 insertion(+), 9 deletions(-) diff --git a/klippy/configfile.py b/klippy/configfile.py index b1f7d6d69e75..b93a23d98fd8 100644 --- a/klippy/configfile.py +++ b/klippy/configfile.py @@ -191,7 +191,6 @@ def _find_autosave_data(self, data): comment_r = re.compile('[#;].*$') value_r = re.compile('[^A-Za-z0-9_].*$') def _strip_duplicates(self, data, config): - fileconfig = config.fileconfig # Comment out fields in 'data' that are defined in 'config' lines = data.split('\n') section = None diff --git a/klippy/extras/dotstar.py b/klippy/extras/dotstar.py index d75f438a795c..4186534fead6 100644 --- a/klippy/extras/dotstar.py +++ b/klippy/extras/dotstar.py @@ -10,7 +10,6 @@ class PrinterDotstar: def __init__(self, config): self.printer = printer = config.get_printer() - name = config.get_name().split()[1] # Configure a software spi bus ppins = printer.lookup_object('pins') data_pin_params = ppins.lookup_pin(config.get('data_pin')) diff --git a/klippy/extras/endstop_phase.py b/klippy/extras/endstop_phase.py index feb9e8b8b082..2c7468bce3e0 100644 --- a/klippy/extras/endstop_phase.py +++ b/klippy/extras/endstop_phase.py @@ -191,7 +191,6 @@ def cmd_ENDSTOP_PHASE_CALIBRATE(self, gcmd): def generate_stats(self, stepper_name, phase_calc): phase_history = phase_calc.phase_history wph = phase_history + phase_history - count = sum(phase_history) phases = len(phase_history) half_phases = phases // 2 res = [] diff --git a/klippy/extras/idle_timeout.py b/klippy/extras/idle_timeout.py index 479c0b5c7999..6ab2a34a533a 100644 --- a/klippy/extras/idle_timeout.py +++ b/klippy/extras/idle_timeout.py @@ -45,7 +45,7 @@ def transition_idle_state(self, eventtime): self.state = "Printing" try: script = self.idle_gcode.render() - res = self.gcode.run_script(script) + self.gcode.run_script(script) except: logging.exception("idle timeout gcode execution") self.state = "Ready" diff --git a/klippy/extras/input_shaper.py b/klippy/extras/input_shaper.py index 7f37d302bd46..a628289ef2d0 100644 --- a/klippy/extras/input_shaper.py +++ b/klippy/extras/input_shaper.py @@ -59,7 +59,6 @@ def get_shaper(self): return self.n, self.A, self.T def update(self, gcmd): self.params.update(gcmd) - old_n, old_A, old_T = self.n, self.A, self.T self.n, self.A, self.T = self.params.get_shaper() def set_shaper_kinematics(self, sk): ffi_main, ffi_lib = chelper.get_ffi() diff --git a/klippy/kinematics/hybrid_corexy.py b/klippy/kinematics/hybrid_corexy.py index 26032039c016..1c2164eb745a 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -11,7 +11,6 @@ class HybridCoreXYKinematics: def __init__(self, toolhead, config): self.printer = config.get_printer() - printer_config = config.getsection('printer') # itersolve parameters self.rails = [ stepper.PrinterRail(config.getsection('stepper_x')), stepper.LookupMultiRail(config.getsection('stepper_y')), diff --git a/klippy/kinematics/hybrid_corexz.py b/klippy/kinematics/hybrid_corexz.py index e13c9aa4cb4a..0eaea117ed2a 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -11,7 +11,6 @@ class HybridCoreXZKinematics: def __init__(self, toolhead, config): self.printer = config.get_printer() - printer_config = config.getsection('printer') # itersolve parameters self.rails = [ stepper.PrinterRail(config.getsection('stepper_x')), stepper.LookupMultiRail(config.getsection('stepper_y')), diff --git a/klippy/mcu.py b/klippy/mcu.py index 366f78062775..e0d580e5c621 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -655,7 +655,6 @@ def _send_config(self, prev_crc): self._config_cmds.insert(0, "allocate_oids count=%d" % (self._oid_count,)) # Resolve pin names - mcu_type = self._serial.get_msgparser().get_constant('MCU') ppins = self._printer.lookup_object('pins') pin_resolver = ppins.get_pin_resolver(self._name) for cmdlist in (self._config_cmds, self._restart_cmds, self._init_cmds): diff --git a/klippy/toolhead.py b/klippy/toolhead.py index a995e4b1e2dd..4149d53b12f6 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -583,7 +583,6 @@ def register_step_generator(self, handler): self.step_generators.append(handler) def note_step_generation_scan_time(self, delay, old_delay=0.): self.flush_step_generation() - cur_delay = self.kin_flush_delay if old_delay: self.kin_flush_times.pop(self.kin_flush_times.index(old_delay)) if delay: From fa5fa74761c05da4efd2c36b4286910d4f6bb2cf Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 6 Jan 2024 19:27:56 -0500 Subject: [PATCH 076/190] mcu: Separate trdispatch handling from MCU_endstop class Create a new TriggerDispatch class to track the low-level handling of the trdispatch mechanism. Signed-off-by: Kevin O'Connor --- klippy/mcu.py | 97 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/klippy/mcu.py b/klippy/mcu.py index e0d580e5c621..2a01c5a7cc1f 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -226,23 +226,17 @@ def stop(self): TRSYNC_TIMEOUT = 0.025 TRSYNC_SINGLE_MCU_TIMEOUT = 0.250 -class MCU_endstop: - RETRY_QUERY = 1.000 - def __init__(self, mcu, pin_params): +class TriggerDispatch: + def __init__(self, mcu): self._mcu = mcu - self._pin = pin_params['pin'] - self._pullup = pin_params['pullup'] - self._invert = pin_params['invert'] - self._oid = self._mcu.create_oid() - self._home_cmd = self._query_cmd = None - self._mcu.register_config_callback(self._build_config) self._trigger_completion = None - self._rest_ticks = 0 ffi_main, ffi_lib = chelper.get_ffi() self._trdispatch = ffi_main.gc(ffi_lib.trdispatch_alloc(), ffi_lib.free) self._trsyncs = [MCU_trsync(mcu, self._trdispatch)] - def get_mcu(self): - return self._mcu + def get_oid(self): + return self._trsyncs[0].get_oid() + def get_command_queue(self): + return self._trsyncs[0].get_command_queue() def add_stepper(self, stepper): trsyncs = {trsync.get_mcu(): trsync for trsync in self._trsyncs} trsync = trsyncs.get(stepper.get_mcu()) @@ -261,6 +255,51 @@ def add_stepper(self, stepper): " multi-mcu shared axis") def get_steppers(self): return [s for trsync in self._trsyncs for s in trsync.get_steppers()] + def start(self, print_time): + reactor = self._mcu.get_printer().get_reactor() + self._trigger_completion = reactor.completion() + expire_timeout = TRSYNC_TIMEOUT + if len(self._trsyncs) == 1: + expire_timeout = TRSYNC_SINGLE_MCU_TIMEOUT + for i, trsync in enumerate(self._trsyncs): + report_offset = float(i) / len(self._trsyncs) + trsync.start(print_time, report_offset, + self._trigger_completion, expire_timeout) + etrsync = self._trsyncs[0] + ffi_main, ffi_lib = chelper.get_ffi() + ffi_lib.trdispatch_start(self._trdispatch, etrsync.REASON_HOST_REQUEST) + return self._trigger_completion + def wait_end(self, end_time): + etrsync = self._trsyncs[0] + etrsync.set_home_end_time(end_time) + if self._mcu.is_fileoutput(): + self._trigger_completion.complete(True) + self._trigger_completion.wait() + def stop(self): + ffi_main, ffi_lib = chelper.get_ffi() + ffi_lib.trdispatch_stop(self._trdispatch) + res = [trsync.stop() for trsync in self._trsyncs] + if any([r == MCU_trsync.REASON_COMMS_TIMEOUT for r in res]): + return MCU_trsync.REASON_COMMS_TIMEOUT + return res[0] + +class MCU_endstop: + def __init__(self, mcu, pin_params): + self._mcu = mcu + self._pin = pin_params['pin'] + self._pullup = pin_params['pullup'] + self._invert = pin_params['invert'] + self._oid = self._mcu.create_oid() + self._home_cmd = self._query_cmd = None + self._mcu.register_config_callback(self._build_config) + self._rest_ticks = 0 + self._dispatch = TriggerDispatch(mcu) + def get_mcu(self): + return self._mcu + def add_stepper(self, stepper): + self._dispatch.add_stepper(stepper) + def get_steppers(self): + return self._dispatch.get_steppers() def _build_config(self): # Setup config self._mcu.add_config_cmd("config_endstop oid=%d pin=%s pull_up=%d" @@ -270,7 +309,7 @@ def _build_config(self): " rest_ticks=0 pin_value=0 trsync_oid=0 trigger_reason=0" % (self._oid,), on_restart=True) # Lookup commands - cmd_queue = self._trsyncs[0].get_command_queue() + cmd_queue = self._dispatch.get_command_queue() self._home_cmd = self._mcu.lookup_command( "endstop_home oid=%c clock=%u sample_ticks=%u sample_count=%c" " rest_ticks=%u pin_value=%c trsync_oid=%c trigger_reason=%c", @@ -284,36 +323,20 @@ def home_start(self, print_time, sample_time, sample_count, rest_time, clock = self._mcu.print_time_to_clock(print_time) rest_ticks = self._mcu.print_time_to_clock(print_time+rest_time) - clock self._rest_ticks = rest_ticks - reactor = self._mcu.get_printer().get_reactor() - self._trigger_completion = reactor.completion() - expire_timeout = TRSYNC_TIMEOUT - if len(self._trsyncs) == 1: - expire_timeout = TRSYNC_SINGLE_MCU_TIMEOUT - for i, trsync in enumerate(self._trsyncs): - report_offset = float(i) / len(self._trsyncs) - trsync.start(print_time, report_offset, - self._trigger_completion, expire_timeout) - etrsync = self._trsyncs[0] - ffi_main, ffi_lib = chelper.get_ffi() - ffi_lib.trdispatch_start(self._trdispatch, etrsync.REASON_HOST_REQUEST) + trigger_completion = self._dispatch.start(print_time) self._home_cmd.send( [self._oid, clock, self._mcu.seconds_to_clock(sample_time), sample_count, rest_ticks, triggered ^ self._invert, - etrsync.get_oid(), etrsync.REASON_ENDSTOP_HIT], reqclock=clock) - return self._trigger_completion + self._dispatch.get_oid(), MCU_trsync.REASON_ENDSTOP_HIT], + reqclock=clock) + return trigger_completion def home_wait(self, home_end_time): - etrsync = self._trsyncs[0] - etrsync.set_home_end_time(home_end_time) - if self._mcu.is_fileoutput(): - self._trigger_completion.complete(True) - self._trigger_completion.wait() + self._dispatch.wait_end(home_end_time) self._home_cmd.send([self._oid, 0, 0, 0, 0, 0, 0, 0]) - ffi_main, ffi_lib = chelper.get_ffi() - ffi_lib.trdispatch_stop(self._trdispatch) - res = [trsync.stop() for trsync in self._trsyncs] - if any([r == etrsync.REASON_COMMS_TIMEOUT for r in res]): + res = self._dispatch.stop() + if res == MCU_trsync.REASON_COMMS_TIMEOUT: return -1. - if res[0] != etrsync.REASON_ENDSTOP_HIT: + if res != MCU_trsync.REASON_ENDSTOP_HIT: return 0. if self._mcu.is_fileoutput(): return home_end_time From acdf8bb1080ab829c6e8092dadf6d488aa416f77 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 8 Jan 2024 21:33:56 -0500 Subject: [PATCH 077/190] probe: Add a probing_move() wrapper to low-level mcu_probe class This allows the low-level probe class more control on the probing implementation. Signed-off-by: Kevin O'Connor --- klippy/extras/bltouch.py | 3 +++ klippy/extras/probe.py | 16 +++++++++------- klippy/extras/smart_effector.py | 3 +++ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/klippy/extras/bltouch.py b/klippy/extras/bltouch.py index 48759752ef51..49385428a452 100644 --- a/klippy/extras/bltouch.py +++ b/klippy/extras/bltouch.py @@ -183,6 +183,9 @@ def multi_probe_end(self): self.verify_raise_probe() self.sync_print_time() self.multi = 'OFF' + def probing_move(self, pos, speed): + phoming = self.printer.lookup_object('homing') + return phoming.probing_move(self, pos, speed) def probe_prepare(self, hmove): if self.multi == 'OFF' or self.multi == 'FIRST': self.lower_probe() diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index 337c41b13c9d..c275d4aaef93 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -117,11 +117,10 @@ def _probe(self, speed): curtime = self.printer.get_reactor().monotonic() if 'z' not in toolhead.get_status(curtime)['homed_axes']: raise self.printer.command_error("Must home before probe") - phoming = self.printer.lookup_object('homing') pos = toolhead.get_position() pos[2] = self.z_position try: - epos = phoming.probing_move(self.mcu_probe, pos, speed) + epos = self.mcu_probe.probing_move(pos, speed) except self.printer.command_error as e: reason = str(e) if "Timeout during endstop homing" in reason: @@ -325,14 +324,14 @@ def _handle_mcu_identify(self): for stepper in kin.get_steppers(): if stepper.is_active_axis('z'): self.add_stepper(stepper) - def raise_probe(self): + def _raise_probe(self): toolhead = self.printer.lookup_object('toolhead') start_pos = toolhead.get_position() self.deactivate_gcode.run_gcode_from_command() if toolhead.get_position()[:3] != start_pos[:3]: raise self.printer.command_error( "Toolhead moved during probe activate_gcode script") - def lower_probe(self): + def _lower_probe(self): toolhead = self.printer.lookup_object('toolhead') start_pos = toolhead.get_position() self.activate_gcode.run_gcode_from_command() @@ -346,16 +345,19 @@ def multi_probe_begin(self): def multi_probe_end(self): if self.stow_on_each_sample: return - self.raise_probe() + self._raise_probe() self.multi = 'OFF' + def probing_move(self, pos, speed): + phoming = self.printer.lookup_object('homing') + return phoming.probing_move(self, pos, speed) def probe_prepare(self, hmove): if self.multi == 'OFF' or self.multi == 'FIRST': - self.lower_probe() + self._lower_probe() if self.multi == 'FIRST': self.multi = 'ON' def probe_finish(self, hmove): if self.multi == 'OFF': - self.raise_probe() + self._raise_probe() def get_position_endstop(self): return self.position_endstop diff --git a/klippy/extras/smart_effector.py b/klippy/extras/smart_effector.py index 6076a246867e..c33de5275fc2 100644 --- a/klippy/extras/smart_effector.py +++ b/klippy/extras/smart_effector.py @@ -78,6 +78,9 @@ def __init__(self, config): self.gcode.register_command("SET_SMART_EFFECTOR", self.cmd_SET_SMART_EFFECTOR, desc=self.cmd_SET_SMART_EFFECTOR_help) + def probing_move(self, pos, speed): + phoming = self.printer.lookup_object('homing') + return phoming.probing_move(self, pos, speed) def probe_prepare(self, hmove): toolhead = self.printer.lookup_object('toolhead') self.probe_wrapper.probe_prepare(hmove) From b8f1df3a9626a168d7152f1e2e923ef0e644eb84 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 15 Dec 2023 12:23:32 -0500 Subject: [PATCH 078/190] sensor_ldc1612: Initial support for bulk reading ldc1612 sensor Signed-off-by: Alan.Ma from BigTreeTech Signed-off-by: Kevin O'Connor --- src/Kconfig | 9 +- src/Makefile | 1 + src/sensor_ldc1612.c | 207 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 src/sensor_ldc1612.c diff --git a/src/Kconfig b/src/Kconfig index 91376910c3cf..7dcea3bab59d 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -104,6 +104,10 @@ config WANT_LIS2DW bool depends on HAVE_GPIO_SPI default y +config WANT_LDC1612 + bool + depends on HAVE_GPIO_I2C + default y config WANT_SOFTWARE_I2C bool depends on HAVE_GPIO && HAVE_GPIO_I2C @@ -114,7 +118,7 @@ config WANT_SOFTWARE_SPI default y config NEED_SENSOR_BULK bool - depends on WANT_SENSORS || WANT_LIS2DW + depends on WANT_SENSORS || WANT_LIS2DW || WANT_LDC1612 default y menu "Optional features (to reduce code size)" depends on HAVE_LIMITED_CODE_SIZE @@ -130,6 +134,9 @@ config WANT_SENSORS config WANT_LIS2DW bool "Support lis2dw 3-axis accelerometer" depends on HAVE_GPIO_SPI +config WANT_LDC1612 + bool "Support ldc1612 eddy current sensor" + depends on HAVE_GPIO_I2C config WANT_SOFTWARE_I2C bool "Support software based I2C \"bit-banging\"" depends on HAVE_GPIO && HAVE_GPIO_I2C diff --git a/src/Makefile b/src/Makefile index eddad9783d96..ed98172e4e8d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -19,4 +19,5 @@ sensors-src-$(CONFIG_HAVE_GPIO_SPI) := thermocouple.c sensor_adxl345.c \ sensors-src-$(CONFIG_HAVE_GPIO_I2C) += sensor_mpu9250.c src-$(CONFIG_WANT_SENSORS) += $(sensors-src-y) src-$(CONFIG_WANT_LIS2DW) += sensor_lis2dw.c +src-$(CONFIG_WANT_LDC1612) += sensor_ldc1612.c src-$(CONFIG_NEED_SENSOR_BULK) += sensor_bulk.c diff --git a/src/sensor_ldc1612.c b/src/sensor_ldc1612.c new file mode 100644 index 000000000000..9258ce6dc2c3 --- /dev/null +++ b/src/sensor_ldc1612.c @@ -0,0 +1,207 @@ +// Support for eddy current sensor data from ldc1612 chip +// +// Copyright (C) 2023 Alan.Ma +// Copyright (C) 2024 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // memcpy +#include "basecmd.h" // oid_alloc +#include "board/gpio.h" // i2c_read +#include "board/irq.h" // irq_disable +#include "board/misc.h" // timer_read_time +#include "command.h" // DECL_COMMAND +#include "i2ccmds.h" // i2cdev_oid_lookup +#include "sched.h" // DECL_TASK +#include "sensor_bulk.h" // sensor_bulk_report +#include "trsync.h" // trsync_do_trigger + +enum { + LDC_PENDING = 1<<0, + LH_AWAIT_HOMING = 1<<1, LH_CAN_TRIGGER = 1<<2 +}; + +struct ldc1612 { + struct timer timer; + uint32_t rest_ticks; + struct i2cdev_s *i2c; + uint8_t flags; + struct sensor_bulk sb; + // homing + struct trsync *ts; + uint8_t homing_flags; + uint8_t trigger_reason; + uint32_t trigger_threshold; + uint32_t homing_clock; +}; + +static struct task_wake ldc1612_wake; + +// Query ldc1612 data +static uint_fast8_t +ldc1612_event(struct timer *timer) +{ + struct ldc1612 *ld = container_of(timer, struct ldc1612, timer); + if (ld->flags & LDC_PENDING) + ld->sb.possible_overflows++; + ld->flags |= LDC_PENDING; + sched_wake_task(&ldc1612_wake); + ld->timer.waketime += ld->rest_ticks; + return SF_RESCHEDULE; +} + +void +command_config_ldc1612(uint32_t *args) +{ + struct ldc1612 *ld = oid_alloc(args[0], command_config_ldc1612 + , sizeof(*ld)); + ld->timer.func = ldc1612_event; + ld->i2c = i2cdev_oid_lookup(args[1]); +} +DECL_COMMAND(command_config_ldc1612, "config_ldc1612 oid=%c i2c_oid=%c"); + +void +command_ldc1612_setup_home(uint32_t *args) +{ + struct ldc1612 *ld = oid_lookup(args[0], command_config_ldc1612); + + ld->trigger_threshold = args[2]; + if (!ld->trigger_threshold) { + ld->ts = NULL; + ld->homing_flags = 0; + return; + } + ld->homing_clock = args[1]; + ld->ts = trsync_oid_lookup(args[3]); + ld->trigger_reason = args[4]; + ld->homing_flags = LH_AWAIT_HOMING | LH_CAN_TRIGGER; +} +DECL_COMMAND(command_ldc1612_setup_home, + "ldc1612_setup_home oid=%c clock=%u threshold=%u" + " trsync_oid=%c trigger_reason=%c"); + +void +command_query_ldc1612_home_state(uint32_t *args) +{ + struct ldc1612 *ld = oid_lookup(args[0], command_config_ldc1612); + sendf("ldc1612_home_state oid=%c homing=%c trigger_clock=%u" + , args[0], !!(ld->homing_flags & LH_CAN_TRIGGER), ld->homing_clock); +} +DECL_COMMAND(command_query_ldc1612_home_state, + "query_ldc1612_home_state oid=%c"); + +// Chip registers +#define REG_DATA0_MSB 0x00 +#define REG_DATA0_LSB 0x01 +#define REG_STATUS 0x18 + +// Read a register on the ldc1612 +static void +read_reg(struct ldc1612 *ld, uint8_t reg, uint8_t *res) +{ + i2c_read(ld->i2c->i2c_config, sizeof(reg), ®, 2, res); +} + +// Read the status register on the ldc1612 +static uint16_t +read_reg_status(struct ldc1612 *ld) +{ + uint8_t data_status[2]; + read_reg(ld, REG_STATUS, data_status); + return (data_status[0] << 8) | data_status[1]; +} + +#define BYTES_PER_SAMPLE 4 + +// Query ldc1612 data +static void +ldc1612_query(struct ldc1612 *ld, uint8_t oid) +{ + // Clear pending flag + irq_disable(); + ld->flags &= ~LDC_PENDING; + irq_enable(); + + // Check if data available + uint16_t status = read_reg_status(ld); + if (status != 0x48) + return; + + // Read coil0 frequency + uint8_t *d = &ld->sb.data[ld->sb.data_count]; + read_reg(ld, REG_DATA0_MSB, &d[0]); + read_reg(ld, REG_DATA0_LSB, &d[2]); + ld->sb.data_count += BYTES_PER_SAMPLE; + + // Check for endstop trigger + uint8_t homing_flags = ld->homing_flags; + if (homing_flags & LH_CAN_TRIGGER) { + uint32_t time = timer_read_time(); + if (!(homing_flags & LH_AWAIT_HOMING) + || !timer_is_before(time, ld->homing_clock)) { + homing_flags &= ~LH_AWAIT_HOMING; + uint32_t data = (d[0] << 24L) | (d[1] << 16L) | (d[2] << 8) | d[3]; + if (data > ld->trigger_threshold) { + homing_flags = 0; + ld->homing_clock = time; + trsync_do_trigger(ld->ts, ld->trigger_reason); + } + ld->homing_flags = homing_flags; + } + } + + // Flush local buffer if needed + if (ld->sb.data_count + BYTES_PER_SAMPLE > ARRAY_SIZE(ld->sb.data)) + sensor_bulk_report(&ld->sb, oid); +} + +void +command_query_ldc1612(uint32_t *args) +{ + struct ldc1612 *ld = oid_lookup(args[0], command_config_ldc1612); + + sched_del_timer(&ld->timer); + ld->flags = 0; + if (!args[1]) + // End measurements + return; + + // Start new measurements query + ld->rest_ticks = args[1]; + sensor_bulk_reset(&ld->sb); + irq_disable(); + ld->timer.waketime = timer_read_time() + ld->rest_ticks; + sched_add_timer(&ld->timer); + irq_enable(); +} +DECL_COMMAND(command_query_ldc1612, "query_ldc1612 oid=%c rest_ticks=%u"); + +void +command_query_ldc1612_status(uint32_t *args) +{ + struct ldc1612 *ld = oid_lookup(args[0], command_config_ldc1612); + + uint32_t time1 = timer_read_time(); + uint16_t status = read_reg_status(ld); + uint32_t time2 = timer_read_time(); + + uint32_t fifo = status == 0x48 ? BYTES_PER_SAMPLE : 0; + sensor_bulk_status(&ld->sb, args[0], time1, time2-time1, fifo); +} +DECL_COMMAND(command_query_ldc1612_status, "query_ldc1612_status oid=%c"); + +void +ldc1612_task(void) +{ + if (!sched_check_wake(&ldc1612_wake)) + return; + uint8_t oid; + struct ldc1612 *ld; + foreach_oid(oid, ld, command_config_ldc1612) { + uint_fast8_t flags = ld->flags; + if (!(flags & LDC_PENDING)) + continue; + ldc1612_query(ld, oid); + } +} +DECL_TASK(ldc1612_task); From da2b25844141c605602b4e3a53c962efaf296f03 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 15 Dec 2023 17:50:34 -0500 Subject: [PATCH 079/190] ldc1612: Initial host support for reading ldc1612 bulk sensor data Signed-off-by: Kevin O'Connor --- klippy/extras/ldc1612.py | 154 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 klippy/extras/ldc1612.py diff --git a/klippy/extras/ldc1612.py b/klippy/extras/ldc1612.py new file mode 100644 index 000000000000..a8cafc5ac32b --- /dev/null +++ b/klippy/extras/ldc1612.py @@ -0,0 +1,154 @@ +# Support for reading frequency samples from ldc1612 +# +# Copyright (C) 2020-2024 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import logging +from . import bus, bulk_sensor + +MIN_MSG_TIME = 0.100 + +BATCH_UPDATES = 0.100 + +BYTES_PER_SAMPLE = 4 +SAMPLES_PER_BLOCK = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE + +LDC1612_ADDR = 0x2a + +LDC1612_FREQ = 12000000 +SETTLETIME = 0.005 +DRIVECUR = 15 +DEGLITCH = 0x05 # 10 Mhz + +LDC1612_MANUF_ID = 0x5449 +LDC1612_DEV_ID = 0x3055 + +REG_RCOUNT0 = 0x08 +REG_OFFSET0 = 0x0c +REG_SETTLECOUNT0 = 0x10 +REG_CLOCK_DIVIDERS0 = 0x14 +REG_ERROR_CONFIG = 0x19 +REG_CONFIG = 0x1a +REG_MUX_CONFIG = 0x1b +REG_DRIVE_CURRENT0 = 0x1e +REG_MANUFACTURER_ID = 0x7e +REG_DEVICE_ID = 0x7f + +# Interface class to LDC1612 mcu support +class LDC1612: + def __init__(self, config): + self.printer = config.get_printer() + self.data_rate = 250 + # Setup mcu sensor_ldc1612 bulk query code + self.i2c = bus.MCU_I2C_from_config(config, + default_addr=LDC1612_ADDR, + default_speed=400000) + self.mcu = mcu = self.i2c.get_mcu() + self.oid = oid = mcu.create_oid() + self.query_ldc1612_cmd = None + mcu.add_config_cmd("config_ldc1612 oid=%d i2c_oid=%d" + % (oid, self.i2c.get_oid())) + mcu.add_config_cmd("query_ldc1612 oid=%d rest_ticks=0" + % (oid,), on_restart=True) + mcu.register_config_callback(self._build_config) + self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=oid) + # Clock tracking + chip_smooth = self.data_rate * BATCH_UPDATES * 2 + self.clock_sync = bulk_sensor.ClockSyncRegression(mcu, chip_smooth) + self.clock_updater = bulk_sensor.ChipClockUpdater(self.clock_sync, + BYTES_PER_SAMPLE) + self.last_error_count = 0 + # Process messages in batches + self.batch_bulk = bulk_sensor.BatchBulkHelper( + self.printer, self._process_batch, + self._start_measurements, self._finish_measurements, BATCH_UPDATES) + self.name = config.get_name().split()[-1] + hdr = ('time', 'frequency') + self.batch_bulk.add_mux_endpoint("ldc1612/dump_ldc1612", "sensor", + self.name, {'header': hdr}) + def _build_config(self): + cmdqueue = self.i2c.get_command_queue() + self.query_ldc1612_cmd = self.mcu.lookup_command( + "query_ldc1612 oid=%c rest_ticks=%u", cq=cmdqueue) + self.clock_updater.setup_query_command( + self.mcu, "query_ldc1612_status oid=%c", oid=self.oid, cq=cmdqueue) + def read_reg(self, reg): + params = self.i2c.i2c_read([reg], 2) + response = bytearray(params['response']) + return (response[0] << 8) | response[1] + def set_reg(self, reg, val, minclock=0): + self.i2c.i2c_write([reg, (val >> 8) & 0xff, val & 0xff], + minclock=minclock) + # Measurement decoding + def _extract_samples(self, raw_samples): + # Load variables to optimize inner loop below + last_sequence = self.clock_updater.get_last_sequence() + time_base, chip_base, inv_freq = self.clock_sync.get_time_translation() + freq_conv = float(LDC1612_FREQ) / (1<<28) + # Process every message in raw_samples + count = seq = 0 + samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK) + for params in raw_samples: + seq_diff = (params['sequence'] - last_sequence) & 0xffff + seq_diff -= (seq_diff & 0x8000) << 1 + seq = last_sequence + seq_diff + d = bytearray(params['data']) + msg_cdiff = seq * SAMPLES_PER_BLOCK - chip_base + for i in range(len(d) // BYTES_PER_SAMPLE): + v = d[i*BYTES_PER_SAMPLE:(i+1)*BYTES_PER_SAMPLE] + if v[0] & 0xf0: + self.last_error_count += 1 + val = ((v[0] & 0x0f) << 24) | (v[1] << 16) | (v[2] << 8) | v[3] + ptime = round(time_base + (msg_cdiff + i) * inv_freq, 6) + samples[count] = (ptime, round(freq_conv * val, 3)) + count += 1 + self.clock_sync.set_last_chip_clock(seq * SAMPLES_PER_BLOCK + i) + del samples[count:] + return samples + # Start, stop, and process message batches + def _start_measurements(self): + # In case of miswiring, testing LDC1612 device ID prevents treating + # noise or wrong signal as a correctly initialized device + manuf_id = self.read_reg(REG_MANUFACTURER_ID) + dev_id = self.read_reg(REG_DEVICE_ID) + if manuf_id != LDC1612_MANUF_ID or dev_id != LDC1612_DEV_ID: + raise self.printer.command_error( + "Invalid ldc1612 id (got %x,%x vs %x,%x).\n" + "This is generally indicative of connection problems\n" + "(e.g. faulty wiring) or a faulty ldc1612 chip." + % (manuf_id, dev_id, LDC1612_MANUF_ID, LDC1612_DEV_ID)) + # Setup chip in requested query rate + rcount0 = LDC1612_FREQ / (16. * (self.data_rate - 4)) + self.set_reg(REG_RCOUNT0, int(rcount0 + 0.5)) + self.set_reg(REG_OFFSET0, 0) + self.set_reg(REG_SETTLECOUNT0, int(SETTLETIME*LDC1612_FREQ/16. + .5)) + self.set_reg(REG_CLOCK_DIVIDERS0, (1 << 12) | 1) + self.set_reg(REG_ERROR_CONFIG, (0x1f << 11) | 1) + self.set_reg(REG_MUX_CONFIG, 0x0208 | DEGLITCH) + self.set_reg(REG_CONFIG, 0x001 | (1<<12) | (1<<10) | (1<<9)) + self.set_reg(REG_DRIVE_CURRENT0, DRIVECUR << 11) + #self.set_reg(REG_CONFIG, 0x001 | (1<<9)) + #self.set_reg(REG_DRIVE_CURRENT0, DRIVECUR << 11) + # Start bulk reading + self.bulk_queue.clear_samples() + rest_ticks = self.mcu.seconds_to_clock(0.5 / self.data_rate) + self.query_ldc1612_cmd.send([self.oid, rest_ticks]) + logging.info("LDC1612 starting '%s' measurements", self.name) + # Initialize clock tracking + self.clock_updater.note_start() + self.last_error_count = 0 + def _finish_measurements(self): + # Halt bulk reading + self.query_ldc1612_cmd.send_wait_ack([self.oid, 0]) + self.bulk_queue.clear_samples() + logging.info("LDC1612 finished '%s' measurements", self.name) + def _process_batch(self, eventtime): + self.clock_updater.update_clock() + raw_samples = self.bulk_queue.pull_samples() + if not raw_samples: + return {} + samples = self._extract_samples(raw_samples) + if not samples: + return {} + return {'data': samples, 'errors': self.last_error_count, + 'overflows': self.clock_updater.get_last_overflows()} From d84fc431a1611737c3385028f61377a506abab76 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 7 Jan 2024 00:06:44 -0500 Subject: [PATCH 080/190] ldc1612: Add LDC_CALIBRATE_DRIVE_CURRENT calibration command Add a command to calibrate the sensor DRIVE_CURRENT0 register. Signed-off-by: Kevin O'Connor --- klippy/extras/ldc1612.py | 48 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/klippy/extras/ldc1612.py b/klippy/extras/ldc1612.py index a8cafc5ac32b..b6a871568eb8 100644 --- a/klippy/extras/ldc1612.py +++ b/klippy/extras/ldc1612.py @@ -34,10 +34,52 @@ REG_MANUFACTURER_ID = 0x7e REG_DEVICE_ID = 0x7f +# Tool for determining appropriate DRIVE_CURRENT register +class DriveCurrentCalibrate: + def __init__(self, config, sensor): + self.printer = config.get_printer() + self.sensor = sensor + self.drive_cur = config.getint("reg_drive_current", DRIVECUR, + minval=0, maxval=31) + self.name = config.get_name() + gcode = self.printer.lookup_object('gcode') + gcode.register_mux_command("LDC_CALIBRATE_DRIVE_CURRENT", + "CHIP", self.name.split()[-1], + self.cmd_LDC_CALIBRATE, + desc=self.cmd_LDC_CALIBRATE_help) + def get_drive_current(self): + return self.drive_cur + cmd_LDC_CALIBRATE_help = "Calibrate LDC1612 DRIVE_CURRENT register" + def cmd_LDC_CALIBRATE(self, gcmd): + is_in_progress = True + def handle_batch(msg): + return is_in_progress + self.sensor.add_client(handle_batch) + toolhead = self.printer.lookup_object("toolhead") + toolhead.dwell(0.100) + toolhead.wait_moves() + old_config = self.sensor.read_reg(REG_CONFIG) + self.sensor.set_reg(REG_CONFIG, 0x001 | (1<<9)) + toolhead.wait_moves() + toolhead.dwell(0.100) + toolhead.wait_moves() + reg_drive_current0 = self.sensor.read_reg(REG_DRIVE_CURRENT0) + self.sensor.set_reg(REG_CONFIG, old_config) + is_in_progress = False + # Report found value to user + drive_cur = (reg_drive_current0 >> 6) & 0x1f + gcmd.respond_info( + "%s: reg_drive_current: %d\n" + "The SAVE_CONFIG command will update the printer config file\n" + "with the above and restart the printer." % (self.name, drive_cur)) + configfile = self.printer.lookup_object('configfile') + configfile.set(self.name, 'reg_drive_current', "%d" % (drive_cur,)) + # Interface class to LDC1612 mcu support class LDC1612: def __init__(self, config): self.printer = config.get_printer() + self.dccal = DriveCurrentCalibrate(config, self) self.data_rate = 250 # Setup mcu sensor_ldc1612 bulk query code self.i2c = bus.MCU_I2C_from_config(config, @@ -79,6 +121,8 @@ def read_reg(self, reg): def set_reg(self, reg, val, minclock=0): self.i2c.i2c_write([reg, (val >> 8) & 0xff, val & 0xff], minclock=minclock) + def add_client(self, cb): + self.batch_bulk.add_client(cb) # Measurement decoding def _extract_samples(self, raw_samples): # Load variables to optimize inner loop below @@ -126,9 +170,7 @@ def _start_measurements(self): self.set_reg(REG_ERROR_CONFIG, (0x1f << 11) | 1) self.set_reg(REG_MUX_CONFIG, 0x0208 | DEGLITCH) self.set_reg(REG_CONFIG, 0x001 | (1<<12) | (1<<10) | (1<<9)) - self.set_reg(REG_DRIVE_CURRENT0, DRIVECUR << 11) - #self.set_reg(REG_CONFIG, 0x001 | (1<<9)) - #self.set_reg(REG_DRIVE_CURRENT0, DRIVECUR << 11) + self.set_reg(REG_DRIVE_CURRENT0, self.dccal.get_drive_current() << 11) # Start bulk reading self.bulk_queue.clear_samples() rest_ticks = self.mcu.seconds_to_clock(0.5 / self.data_rate) From b0d90fd013c2965b9dd8c03364868be71eaa11bf Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 30 Dec 2023 21:07:19 -0500 Subject: [PATCH 081/190] probe_eddy_current: Support calibrating Z height to sensor frequency Add a calibration tool that can be used to correlate sensor frequency to bed Z height. Signed-off-by: Kevin O'Connor --- klippy/extras/ldc1612.py | 11 +- klippy/extras/probe_eddy_current.py | 182 ++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 klippy/extras/probe_eddy_current.py diff --git a/klippy/extras/ldc1612.py b/klippy/extras/ldc1612.py index b6a871568eb8..5c09940d8a7e 100644 --- a/klippy/extras/ldc1612.py +++ b/klippy/extras/ldc1612.py @@ -77,8 +77,9 @@ def handle_batch(msg): # Interface class to LDC1612 mcu support class LDC1612: - def __init__(self, config): + def __init__(self, config, calibration=None): self.printer = config.get_printer() + self.calibration = calibration self.dccal = DriveCurrentCalibrate(config, self) self.data_rate = 250 # Setup mcu sensor_ldc1612 bulk query code @@ -105,7 +106,7 @@ def __init__(self, config): self.printer, self._process_batch, self._start_measurements, self._finish_measurements, BATCH_UPDATES) self.name = config.get_name().split()[-1] - hdr = ('time', 'frequency') + hdr = ('time', 'frequency', 'z') self.batch_bulk.add_mux_endpoint("ldc1612/dump_ldc1612", "sensor", self.name, {'header': hdr}) def _build_config(self): @@ -114,6 +115,8 @@ def _build_config(self): "query_ldc1612 oid=%c rest_ticks=%u", cq=cmdqueue) self.clock_updater.setup_query_command( self.mcu, "query_ldc1612_status oid=%c", oid=self.oid, cq=cmdqueue) + def get_mcu(self): + return self.i2c.get_mcu() def read_reg(self, reg): params = self.i2c.i2c_read([reg], 2) response = bytearray(params['response']) @@ -144,7 +147,7 @@ def _extract_samples(self, raw_samples): self.last_error_count += 1 val = ((v[0] & 0x0f) << 24) | (v[1] << 16) | (v[2] << 8) | v[3] ptime = round(time_base + (msg_cdiff + i) * inv_freq, 6) - samples[count] = (ptime, round(freq_conv * val, 3)) + samples[count] = (ptime, round(freq_conv * val, 3), 999.9) count += 1 self.clock_sync.set_last_chip_clock(seq * SAMPLES_PER_BLOCK + i) del samples[count:] @@ -192,5 +195,7 @@ def _process_batch(self, eventtime): samples = self._extract_samples(raw_samples) if not samples: return {} + if self.calibration is not None: + self.calibration.apply_calibration(samples) return {'data': samples, 'errors': self.last_error_count, 'overflows': self.clock_updater.get_last_overflows()} diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py new file mode 100644 index 000000000000..5dc0f08bdcdd --- /dev/null +++ b/klippy/extras/probe_eddy_current.py @@ -0,0 +1,182 @@ +# Support for eddy current based Z probes +# +# Copyright (C) 2021-2024 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import logging, math, bisect +import mcu +from . import ldc1612, probe, manual_probe + +# Tool for calibrating the sensor Z detection and applying that calibration +class EddyCalibration: + def __init__(self, config): + self.printer = config.get_printer() + self.name = config.get_name() + # Current calibration data + self.cal_freqs = [] + self.cal_zpos = [] + cal = config.get('calibrate', None) + if cal is not None: + cal = [list(map(float, d.strip().split(':', 1))) + for d in cal.split(',')] + self.load_calibration(cal) + # Probe calibrate state + self.probe_speed = 0. + # Register commands + cname = self.name.split()[-1] + gcode = self.printer.lookup_object('gcode') + gcode.register_mux_command("PROBE_EDDY_CURRENT_CALIBRATE", "CHIP", + cname, self.cmd_EDDY_CALIBRATE, + desc=self.cmd_EDDY_CALIBRATE_help) + def load_calibration(self, cal): + cal = sorted([(c[1], c[0]) for c in cal]) + self.cal_freqs = [c[0] for c in cal] + self.cal_zpos = [c[1] for c in cal] + def apply_calibration(self, samples): + for i, (samp_time, freq, dummy_z) in enumerate(samples): + pos = bisect.bisect(self.cal_freqs, freq) + if pos >= len(self.cal_zpos): + zpos = -99.9 + elif pos == 0: + zpos = 99.9 + else: + # XXX - optimize and avoid div by zero + this_freq = self.cal_freqs[pos] + prev_freq = self.cal_freqs[pos - 1] + this_zpos = self.cal_zpos[pos] + prev_zpos = self.cal_zpos[pos - 1] + gain = (this_zpos - prev_zpos) / (this_freq - prev_freq) + offset = prev_zpos - prev_freq * gain + zpos = freq * gain + offset + samples[i] = (samp_time, freq, round(zpos, 6)) + def do_calibration_moves(self, move_speed): + toolhead = self.printer.lookup_object('toolhead') + kin = toolhead.get_kinematics() + move = toolhead.manual_move + # Start data collection + msgs = [] + is_finished = False + def handle_batch(msg): + if is_finished: + return False + msgs.append(msg) + return True + self.printer.lookup_object(self.name).add_client(handle_batch) + toolhead.dwell(1.) + # Move to each 50um position + max_z = 4 + samp_dist = 0.050 + num_steps = int(max_z / samp_dist + .5) + 1 + start_pos = toolhead.get_position() + times = [] + for i in range(num_steps): + # Move to next position (always descending to reduce backlash) + hop_pos = list(start_pos) + hop_pos[2] += i * samp_dist + 0.500 + move(hop_pos, move_speed) + next_pos = list(start_pos) + next_pos[2] += i * samp_dist + move(next_pos, move_speed) + # Note sample timing + start_query_time = toolhead.get_last_move_time() + 0.050 + end_query_time = start_query_time + 0.100 + toolhead.dwell(0.200) + # Find Z position based on actual commanded stepper position + toolhead.flush_step_generation() + kin_spos = {s.get_name(): s.get_commanded_position() + for s in kin.get_steppers()} + kin_pos = kin.calc_position(kin_spos) + times.append((start_query_time, end_query_time, kin_pos[2])) + toolhead.dwell(1.0) + toolhead.wait_moves() + # Finish data collection + is_finished = True + # Correlate query responses + cal = {} + step = 0 + for msg in msgs: + for query_time, freq, old_z in msg['data']: + # Add to step tracking + while step < len(times) and query_time > times[step][1]: + step += 1 + if step < len(times) and query_time >= times[step][0]: + cal.setdefault(times[step][2], []).append(freq) + if len(cal) != len(times): + raise self.printer.command_error( + "Failed calibration - incomplete sensor data") + return cal + def calc_freqs(self, meas): + total_count = total_variance = 0 + positions = {} + for pos, freqs in meas.items(): + count = len(freqs) + freq_avg = float(sum(freqs)) / count + positions[pos] = freq_avg + total_count += count + total_variance += sum([(f - freq_avg)**2 for f in freqs]) + return positions, math.sqrt(total_variance / total_count), total_count + def post_manual_probe(self, kin_pos): + if kin_pos is None: + # Manual Probe was aborted + return + curpos = list(kin_pos) + move = self.printer.lookup_object('toolhead').manual_move + # Move away from the bed + probe_calibrate_z = curpos[2] + curpos[2] += 5. + move(curpos, self.probe_speed) + # Move sensor over nozzle position + pprobe = self.printer.lookup_object("probe") + x_offset, y_offset, z_offset = pprobe.get_offsets() + curpos[0] -= x_offset + curpos[1] -= y_offset + move(curpos, self.probe_speed) + # Descend back to bed + curpos[2] -= 5. - 0.050 + move(curpos, self.probe_speed) + # Perform calibration movement and capture + cal = self.do_calibration_moves(self.probe_speed) + # Calculate each sample position average and variance + positions, std, total = self.calc_freqs(cal) + last_freq = 0. + for pos, freq in reversed(sorted(positions.items())): + if freq <= last_freq: + raise self.printer.command_error( + "Failed calibration - frequency not increasing each step") + last_freq = freq + gcode = self.printer.lookup_object("gcode") + gcode.respond_info( + "probe_eddy_current: stddev=%.3f in %d queries\n" + "The SAVE_CONFIG command will update the printer config file\n" + "and restart the printer." % (std, total)) + # Save results + cal_contents = [] + for i, (pos, freq) in enumerate(sorted(positions.items())): + if not i % 3: + cal_contents.append('\n') + cal_contents.append("%.6f:%.3f" % (pos - probe_calibrate_z, freq)) + cal_contents.append(',') + cal_contents.pop() + configfile = self.printer.lookup_object('configfile') + configfile.set(self.name, 'calibrate', ''.join(cal_contents)) + cmd_EDDY_CALIBRATE_help = "Calibrate eddy current probe" + def cmd_EDDY_CALIBRATE(self, gcmd): + self.probe_speed = gcmd.get_float("PROBE_SPEED", 5., above=0.) + # Start manual probe + manual_probe.ManualProbeHelper(self.printer, gcmd, + self.post_manual_probe) + +# Main "printer object" +class PrinterEddyProbe: + def __init__(self, config): + self.printer = config.get_printer() + self.calibration = EddyCalibration(config) + # Sensor type + sensors = { "ldc1612": ldc1612.LDC1612 } + sensor_type = config.getchoice('sensor_type', {s: s for s in sensors}) + self.sensor_helper = sensors[sensor_type](config, self.calibration) + def add_client(self, cb): + self.sensor_helper.add_client(cb) + +def load_config_prefix(config): + return PrinterEddyProbe(config) From 13b2926e0c844ac54ade7b2b4e8e42201d0a2d33 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 7 Jan 2024 13:32:41 -0500 Subject: [PATCH 082/190] probe_eddy_current: Initial support for PROBE command Signed-off-by: Kevin O'Connor --- klippy/extras/ldc1612.py | 21 ++++++ klippy/extras/probe_eddy_current.py | 105 +++++++++++++++++++++++++++- 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/klippy/extras/ldc1612.py b/klippy/extras/ldc1612.py index 5c09940d8a7e..16b8cd47ebab 100644 --- a/klippy/extras/ldc1612.py +++ b/klippy/extras/ldc1612.py @@ -89,6 +89,7 @@ def __init__(self, config, calibration=None): self.mcu = mcu = self.i2c.get_mcu() self.oid = oid = mcu.create_oid() self.query_ldc1612_cmd = None + self.ldc1612_setup_home_cmd = self.query_ldc1612_home_state_cmd = None mcu.add_config_cmd("config_ldc1612 oid=%d i2c_oid=%d" % (oid, self.i2c.get_oid())) mcu.add_config_cmd("query_ldc1612 oid=%d rest_ticks=0" @@ -115,6 +116,13 @@ def _build_config(self): "query_ldc1612 oid=%c rest_ticks=%u", cq=cmdqueue) self.clock_updater.setup_query_command( self.mcu, "query_ldc1612_status oid=%c", oid=self.oid, cq=cmdqueue) + self.ldc1612_setup_home_cmd = self.mcu.lookup_command( + "ldc1612_setup_home oid=%c clock=%u threshold=%u" + " trsync_oid=%c trigger_reason=%c", cq=cmdqueue) + self.query_ldc1612_home_state_cmd = self.mcu.lookup_query_command( + "query_ldc1612_home_state oid=%c", + "ldc1612_home_state oid=%c homing=%c trigger_clock=%u", + oid=self.oid, cq=cmdqueue) def get_mcu(self): return self.i2c.get_mcu() def read_reg(self, reg): @@ -126,6 +134,19 @@ def set_reg(self, reg, val, minclock=0): minclock=minclock) def add_client(self, cb): self.batch_bulk.add_client(cb) + # Homing + def setup_home(self, print_time, trigger_freq, trsync_oid, reason): + clock = self.mcu.print_time_to_clock(print_time) + tfreq = int(trigger_freq * (1<<28) / float(LDC1612_FREQ) + 0.5) + self.ldc1612_setup_home_cmd.send( + [self.oid, clock, tfreq, trsync_oid, reason]) + def clear_home(self): + self.ldc1612_setup_home_cmd.send([self.oid, 0, 0, 0, 0]) + if self.mcu.is_fileoutput(): + return 0. + params = self.query_ldc1612_home_state_cmd.send([self.oid]) + tclock = self.mcu.clock32_to_clock64(params['trigger_clock']) + return self.mcu.clock_to_print_time(tclock) # Measurement decoding def _extract_samples(self, raw_samples): # Load variables to optimize inner loop below diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index 5dc0f08bdcdd..d140d3edcd68 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -28,6 +28,8 @@ def __init__(self, config): gcode.register_mux_command("PROBE_EDDY_CURRENT_CALIBRATE", "CHIP", cname, self.cmd_EDDY_CALIBRATE, desc=self.cmd_EDDY_CALIBRATE_help) + def is_calibrated(self): + return len(self.cal_freqs) > 2 def load_calibration(self, cal): cal = sorted([(c[1], c[0]) for c in cal]) self.cal_freqs = [c[0] for c in cal] @@ -40,7 +42,7 @@ def apply_calibration(self, samples): elif pos == 0: zpos = 99.9 else: - # XXX - optimize and avoid div by zero + # XXX - could further optimize and avoid div by zero this_freq = self.cal_freqs[pos] prev_freq = self.cal_freqs[pos - 1] this_zpos = self.cal_zpos[pos] @@ -49,6 +51,21 @@ def apply_calibration(self, samples): offset = prev_zpos - prev_freq * gain zpos = freq * gain + offset samples[i] = (samp_time, freq, round(zpos, 6)) + def height_to_freq(self, height): + # XXX - could optimize lookup + rev_zpos = list(reversed(self.cal_zpos)) + rev_freqs = list(reversed(self.cal_freqs)) + pos = bisect.bisect(rev_zpos, height) + if pos == 0 or pos >= len(rev_zpos): + raise self.printer.command_error( + "Invalid probe_eddy_current height") + this_freq = rev_freqs[pos] + prev_freq = rev_freqs[pos - 1] + this_zpos = rev_zpos[pos] + prev_zpos = rev_zpos[pos - 1] + gain = (this_freq - prev_freq) / (this_zpos - prev_zpos) + offset = prev_freq - prev_zpos * gain + return height * gain + offset def do_calibration_moves(self, move_speed): toolhead = self.printer.lookup_object('toolhead') kin = toolhead.get_kinematics() @@ -166,6 +183,88 @@ def cmd_EDDY_CALIBRATE(self, gcmd): manual_probe.ManualProbeHelper(self.printer, gcmd, self.post_manual_probe) +# Helper for implementing PROBE style commands +class EddyEndstopWrapper: + def __init__(self, config, sensor_helper, calibration): + self._printer = config.get_printer() + self._sensor_helper = sensor_helper + self._mcu = sensor_helper.get_mcu() + self._calibration = calibration + self._z_offset = config.getfloat('z_offset', minval=0.) + self._dispatch = mcu.TriggerDispatch(self._mcu) + self._samples = [] + self._is_sampling = self._start_from_home = self._need_stop = False + self._printer.register_event_handler('klippy:mcu_identify', + self._handle_mcu_identify) + def _handle_mcu_identify(self): + kin = self._printer.lookup_object('toolhead').get_kinematics() + for stepper in kin.get_steppers(): + if stepper.is_active_axis('z'): + self.add_stepper(stepper) + # Measurement gathering + def _start_measurements(self, is_home=False): + self._need_stop = False + if self._is_sampling: + return + self._is_sampling = True + self._is_from_home = is_home + self._sensor_helper.add_client(self._add_measurement) + def _stop_measurements(self, is_home=False): + if not self._is_sampling or (is_home and not self._start_from_home): + return + self._need_stop = True + def _add_measurement(self, msg): + if self._need_stop: + del self._samples[:] + self._is_sampling = self._need_stop = False + return False + self._samples.append(msg) + return True + # Interface for MCU_endstop + def get_mcu(self): + return self._mcu + def add_stepper(self, stepper): + self._dispatch.add_stepper(stepper) + def get_steppers(self): + return self._dispatch.get_steppers() + def home_start(self, print_time, sample_time, sample_count, rest_time, + triggered=True): + self._start_measurements(is_home=True) + trigger_freq = self._calibration.height_to_freq(self._z_offset) + trigger_completion = self._dispatch.start(print_time) + self._sensor_helper.setup_home( + print_time, trigger_freq, self._dispatch.get_oid(), + mcu.MCU_trsync.REASON_ENDSTOP_HIT) + return trigger_completion + def home_wait(self, home_end_time): + self._dispatch.wait_end(home_end_time) + trigger_time = self._sensor_helper.clear_home() + self._stop_measurements(is_home=True) + res = self._dispatch.stop() + if res == mcu.MCU_trsync.REASON_COMMS_TIMEOUT: + return -1. + if res != mcu.MCU_trsync.REASON_ENDSTOP_HIT: + return 0. + if self._mcu.is_fileoutput(): + return home_end_time + return trigger_time + def query_endstop(self, print_time): + return False # XXX + # Interface for ProbeEndstopWrapper + def multi_probe_begin(self): + if not self._calibration.is_calibrated(): + raise self._printer.command_error( + "Must calibrate probe_eddy_current first") + self._start_measurements() + def multi_probe_end(self): + self._stop_measurements() + def probe_prepare(self, hmove): + pass + def probe_finish(self, hmove): + pass + def get_position_endstop(self): + return self._z_offset + # Main "printer object" class PrinterEddyProbe: def __init__(self, config): @@ -175,6 +274,10 @@ def __init__(self, config): sensors = { "ldc1612": ldc1612.LDC1612 } sensor_type = config.getchoice('sensor_type', {s: s for s in sensors}) self.sensor_helper = sensors[sensor_type](config, self.calibration) + # Probe interface + self.probe = EddyEndstopWrapper(config, self.sensor_helper, + self.calibration) + self.printer.add_object('probe', probe.PrinterProbe(config, self.probe)) def add_client(self, cb): self.sensor_helper.add_client(cb) From 28281c595b6be72b5085266b39e3401858b4c0cb Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 8 Jan 2024 21:56:04 -0500 Subject: [PATCH 083/190] probe_eddy_current: Use sensor value at halt position for "trigger" position Calculate the sensor Z position after the probe halts and return that as the "probed position". This sensor position provides a more accurate measurement. Signed-off-by: Kevin O'Connor --- klippy/extras/probe_eddy_current.py | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index d140d3edcd68..858fb6e09ea2 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -194,6 +194,7 @@ def __init__(self, config, sensor_helper, calibration): self._dispatch = mcu.TriggerDispatch(self._mcu) self._samples = [] self._is_sampling = self._start_from_home = self._need_stop = False + self._trigger_time = 0. self._printer.register_event_handler('klippy:mcu_identify', self._handle_mcu_identify) def _handle_mcu_identify(self): @@ -229,6 +230,7 @@ def get_steppers(self): return self._dispatch.get_steppers() def home_start(self, print_time, sample_time, sample_count, rest_time, triggered=True): + self._trigger_time = 0. self._start_measurements(is_home=True) trigger_freq = self._calibration.height_to_freq(self._z_offset) trigger_completion = self._dispatch.start(print_time) @@ -247,10 +249,52 @@ def home_wait(self, home_end_time): return 0. if self._mcu.is_fileoutput(): return home_end_time + self._trigger_time = trigger_time return trigger_time def query_endstop(self, print_time): return False # XXX # Interface for ProbeEndstopWrapper + def probing_move(self, pos, speed): + # Perform probing move + phoming = self._printer.lookup_object('homing') + trig_pos = phoming.probing_move(self, pos, speed) + if not self._trigger_time: + return trig_pos + # Wait for 200ms to elapse since trigger time + reactor = self._printer.get_reactor() + while 1: + systime = reactor.monotonic() + est_print_time = self._mcu.estimated_print_time(systime) + need_delay = self._trigger_time + 0.200 - est_print_time + if need_delay <= 0.: + break + reactor.pause(systime + need_delay) + # Find position since trigger + samples = self._samples + self._samples = [] + start_time = self._trigger_time + 0.050 + end_time = start_time + 0.100 + samp_sum = 0. + samp_count = 0 + for msg in samples: + data = msg['data'] + if data[0][0] > end_time: + break + if data[-1][0] < start_time: + continue + for time, freq, z in data: + if time >= start_time and time <= end_time: + samp_sum += z + samp_count += 1 + if not samp_count: + raise self._printer.command_error( + "Unable to obtain probe_eddy_current sensor readings") + halt_z = samp_sum / samp_count + # Calculate reported "trigger" position + toolhead = self._printer.lookup_object("toolhead") + new_pos = toolhead.get_position() + new_pos[2] += self._z_offset - halt_z + return new_pos def multi_probe_begin(self): if not self._calibration.is_calibrated(): raise self._printer.command_error( From 30e0fddbbf3957dfae696bdc739dfb4a0ad83016 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 7 Jan 2024 23:53:57 -0500 Subject: [PATCH 084/190] docs: Add documentation for probe_eddy_current Signed-off-by: Kevin O'Connor --- docs/Config_Reference.md | 34 ++++++++++++++++++++++++++++++++++ docs/G-Codes.md | 22 ++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 1b833eaf1e7f..4999aba56bf7 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -1995,6 +1995,40 @@ z_offset: # See the "probe" section for more information on the parameters above. ``` +### [probe_eddy_current] + +Support for eddy current inductive probes. One may define this section +(instead of a probe section) to enable this probe. See the +[command reference](G-Codes.md#probe_eddy_current) for further information. + +``` +[probe_eddy_current my_eddy_probe] +sensor_type: ldc1612 +# The sensor chip used to perform eddy current measurements. This +# parameter must be provided and must be set to ldc1612. +#z_offset: +# The nominal distance (in mm) between the nozzle and bed that a +# probing attempt should stop at. This parameter must be provided. +#i2c_address: +#i2c_mcu: +#i2c_bus: +#i2c_software_scl_pin: +#i2c_software_sda_pin: +#i2c_speed: +# The i2c settings for the sensor chip. See the "common I2C +# settings" section for a description of the above parameters. +#x_offset: +#y_offset: +#speed: +#lift_speed: +#samples: +#sample_retract_dist: +#samples_result: +#samples_tolerance: +#samples_tolerance_retries: +# See the "probe" section for information on these parameters. +``` + ### [axis_twist_compensation] A tool to compensate for inaccurate probe readings due to twist in X gantry. See diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 0b8aa6e501e1..e55fba35db80 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -984,6 +984,28 @@ babystepping), and subtract if from the probe's z_offset. This acts to take a frequently used babystepping value, and "make it permanent". Requires a `SAVE_CONFIG` to take effect. +### [probe_eddy_current] + +The following commands are available when a +[probe_eddy_current config section](Config_Reference.md#probe_eddy_current) +is enabled. + +#### PROBE_EDDY_CURRENT_CALIBRATE +`PROBE_EDDY_CURRENT_CALIBRATE CHIP=`: This starts a tool +that calibrates the sensor resonance frequencies to corresponding Z +heights. The tool will take a couple of minutes to complete. After +completion, use the SAVE_CONFIG command to store the results in the +printer.cfg file. + +#### LDC_CALIBRATE_DRIVE_CURRENT +`LDC_CALIBRATE_DRIVE_CURRENT CHIP=` This tool will +calibrate the ldc1612 DRIVE_CURRENT0 register. Prior to using this +tool, move the sensor so that it is near the center of the bed and +about 20mm above the bed surface. Run this command to determine an +appropriate DRIVE_CURRENT for the sensor. After running this command +use the SAVE_CONFIG command to store that new setting in the +printer.cfg config file. + ### [pwm_cycle_time] The following command is available when a From b09897245e4e592fab29a281e338183b42a36b6b Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 8 Jan 2024 00:36:32 -0500 Subject: [PATCH 085/190] docs: Add a new Eddy_Probe.md document Signed-off-by: Kevin O'Connor --- docs/Eddy_Probe.md | 56 ++++++++++++++++++++++++++++++++++++++ docs/Overview.md | 1 + docs/_klipper3d/mkdocs.yml | 1 + 3 files changed, 58 insertions(+) create mode 100644 docs/Eddy_Probe.md diff --git a/docs/Eddy_Probe.md b/docs/Eddy_Probe.md new file mode 100644 index 000000000000..221c855b6d66 --- /dev/null +++ b/docs/Eddy_Probe.md @@ -0,0 +1,56 @@ +# Eddy Current Inductive probe + +This document describes how to use an +[eddy current](https://en.wikipedia.org/wiki/Eddy_current) inductive +probe in Klipper. + +Currently, an eddy current probe can not be used for Z homing. The +sensor can only be used for Z probing. + +Start by declaring a +[probe_eddy_current config section](Config_Reference.md#probe_eddy_current) +in the printer.cfg file. It is recommended to set the `z_offset` to +0.5mm. It is typical for the sensor to require an `x_offset` and +`y_offset`. If these values are not known, one should estimate the +values during initial calibration. + +The first step in calibration is to determine the appropriate +DRIVE_CURRENT for the sensor. Home the printer and navigate the +toolhead so that the sensor is near the center of the bed and is about +20mm above the bed. Then issue an `LDC_CALIBRATE_DRIVE_CURRENT +CHIP=` command. For example, if the config section was +named `[probe_eddy_current my_eddy_probe]` then one would run +`LDC_CALIBRATE_DRIVE_CURRENT CHIP=my_eddy_probe`. This command should +complete in a few seconds. After it completes, issue a `SAVE_CONFIG` +command to save the results to the printer.cfg and restart. + +The second step in calibration is to correlate the sensor readings to +the corresponding Z heights. Home the printer and navigate the +toolhead so that the nozzle is near the center of the bed. Then run an +`PROBE_EDDY_CURRENT_CALIBRATE CHIP=my_eddy_probe` command. Once the +tool starts, follow the steps described at +["the paper test"](Bed_Level.md#the-paper-test) to determine the +actual distance between the nozzle and bed at the given location. Once +those steps are complete one can `ACCEPT` the position. The tool will +then move the the toolhead so that the sensor is above the point where +the nozzle used to be and run a series of movements to correlate the +sensor to Z positions. This will take a couple of minutes. After the +tool completes, issue a `SAVE_CONFIG` command to save the results to +the printer.cfg and restart. + +After initial calibration it is a good idea to verify that the +`x_offset` and `y_offset` are accurate. Follow the steps to +[calibrate probe x and y offsets](Probe_Calibrate.md#calibrating-probe-x-and-y-offsets). +If either the `x_offset` or `y_offset` is modified then be sure to run +the `PROBE_EDDY_CURRENT_CALIBRATE` command (as described above) after +making the change. + +Once calibration is complete, one may use all the standard Klipper +tools that use a Z probe. + +Note that eddy current sensors (and inductive probes in general) are +susceptible to "thermal drift". That is, changes in temperature can +result in changes in reported Z height. Changes in either the bed +surface temperature or sensor hardware temperature can skew the +results. It is important that calibration and probing is only done +when the printer is at a stable temperature. diff --git a/docs/Overview.md b/docs/Overview.md index 477bc68b7530..2b9253c26e67 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -99,3 +99,4 @@ communication with the Klipper developers. troubleshooting CAN bus. - [TSL1401CL filament width sensor](TSL1401CL_Filament_Width_Sensor.md) - [Hall filament width sensor](Hall_Filament_Width_Sensor.md) +- [Eddy Current Inductive probe](Eddy_Probe.md) diff --git a/docs/_klipper3d/mkdocs.yml b/docs/_klipper3d/mkdocs.yml index d290a45f5764..c5da747b5c37 100644 --- a/docs/_klipper3d/mkdocs.yml +++ b/docs/_klipper3d/mkdocs.yml @@ -138,4 +138,5 @@ nav: - CANBUS_Troubleshooting.md - TSL1401CL_Filament_Width_Sensor.md - Hall_Filament_Width_Sensor.md + - Eddy_Probe.md - Sponsors.md From a8b493a1aeaa605e1d6bffd309562f22356e8930 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 15 Dec 2023 19:05:25 -0500 Subject: [PATCH 086/190] motan: Add support for graphing ldc1612 coil frequencies Signed-off-by: Kevin O'Connor --- scripts/motan/data_logger.py | 6 ++-- scripts/motan/readlog.py | 65 ++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/scripts/motan/data_logger.py b/scripts/motan/data_logger.py index 81370713cbf9..e00c99560bf2 100755 --- a/scripts/motan/data_logger.py +++ b/scripts/motan/data_logger.py @@ -152,10 +152,12 @@ def handle_subscribe(self, msg, raw_msg): "motion_report/dump_stepper", {"name": stepper}) # Subscribe to additional sensor data stypes = ["adxl345", "lis2dw", "mpu9250", "angle"] + stypes = {st:st for st in stypes} + stypes['probe_eddy_current'] = 'ldc1612' config = status["configfile"]["settings"] for cfgname in config.keys(): - for st in stypes: - if cfgname == st or cfgname.startswith(st + " "): + for capprefix, st in sorted(stypes.items()): + if cfgname == capprefix or cfgname.startswith(capprefix + " "): aname = cfgname.split()[-1] lname = "%s:%s" % (st, aname) qcmd = "%s/dump_%s" % (st, st) diff --git a/scripts/motan/readlog.py b/scripts/motan/readlog.py index f49f0b593602..1b44c9375674 100644 --- a/scripts/motan/readlog.py +++ b/scripts/motan/readlog.py @@ -439,6 +439,71 @@ def pull_data(self, req_time): self.data_pos += 1 LogHandlers["angle"] = HandleAngle +def interpolate(next_val, prev_val, next_time, prev_time, req_time): + vdiff = next_val - prev_val + tdiff = next_time - prev_time + rtdiff = req_time - prev_time + return prev_val + rtdiff * vdiff / tdiff + +# Extract eddy current data +class HandleEddyCurrent: + SubscriptionIdParts = 2 + ParametersMin = 1 + ParametersMax = 2 + DataSets = [ + ('ldc1612()', 'Coil resonant frequency'), + ('ldc1612(,period)', 'Coil resonant period'), + ('ldc1612(,z)', 'Estimated Z height'), + ] + def __init__(self, lmanager, name, name_parts): + self.name = name + self.sensor_name = name_parts[1] + if len(name_parts) == 3 and name_parts[2] not in ("period", "z"): + raise error("Unknown ldc1612 selection '%s'" % (name_parts[2],)) + self.report_frequency = len(name_parts) == 2 + self.report_z = len(name_parts) == 3 and name_parts[2] == "z" + self.jdispatch = lmanager.get_jdispatch() + self.next_samp = self.prev_samp = [0., 0., 0.] + self.cur_data = [] + self.data_pos = 0 + def get_label(self): + if self.report_frequency: + label = '%s frequency' % (self.sensor_name,) + return {'label': label, 'units': 'Frequency\n(Hz)'} + if self.report_z: + label = '%s height' % (self.sensor_name,) + return {'label': label, 'units': 'Position\n(mm)'} + label = '%s period' % (self.sensor_name,) + return {'label': label, 'units': 'Period\n(s)'} + def pull_data(self, req_time): + while 1: + next_time, next_freq, next_z = self.next_samp + if req_time <= next_time: + prev_time, prev_freq, prev_z = self.prev_samp + if self.report_frequency: + next_val = next_freq + prev_val = prev_freq + elif self.report_z: + next_val = next_z + prev_val = prev_z + else: + next_val = 1. / next_freq + prev_val = 1. / prev_freq + return interpolate(next_val, prev_val, next_time, prev_time, + req_time) + if self.data_pos >= len(self.cur_data): + # Read next data block + jmsg = self.jdispatch.pull_msg(req_time, self.name) + if jmsg is None: + return 0. + self.cur_data = jmsg['data'] + self.data_pos = 0 + continue + self.prev_samp = self.next_samp + self.next_samp = self.cur_data[self.data_pos] + self.data_pos += 1 +LogHandlers["ldc1612"] = HandleEddyCurrent + ###################################################################### # Log reading From 4cfa266e007d57b1a70773a228170c1bd7b2ad14 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 10 Apr 2024 10:38:48 -0400 Subject: [PATCH 087/190] manual_stepper: Revert "manual_stepper: Add basic status. (#6527)" This reverts commit b029d0466841b90b54279500f70a92deacfd6c5a. The MCU_Stepper class does not have a is_motor_enabled() method, so the change above results in an internal exception. Signed-off-by: Kevin O'Connor --- docs/Status_Reference.md | 7 ------- klippy/extras/manual_stepper.py | 5 ----- 2 files changed, 12 deletions(-) diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index 80f53d9f3dab..0e72a12b1f9f 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -293,13 +293,6 @@ understands it). - `z_position_lower`: Last probe attempt just lower than the current height. - `z_position_upper`: Last probe attempt just greater than the current height. -## manual_stepper - -The following information is available in the -`manual_stepper` object: -- `enabled`: Returns True if the stepper is currently enabled. -- `position`: The requested position. - ## mcu The following information is available in diff --git a/klippy/extras/manual_stepper.py b/klippy/extras/manual_stepper.py index e18989d3a6c5..40db4a50316e 100644 --- a/klippy/extras/manual_stepper.py +++ b/klippy/extras/manual_stepper.py @@ -104,11 +104,6 @@ def cmd_MANUAL_STEPPER(self, gcmd): self.do_move(movepos, speed, accel, sync) elif gcmd.get_int('SYNC', 0): self.sync_print_time() - - def get_status(self, eventtime): - return {'position': self.rail.get_commanded_position(), - 'enabled': self.steppers[0].is_motor_enabled()} - # Toolhead wrappers to support homing def flush_step_generation(self): self.sync_print_time() From c37329e9e22f7d00128e9f52b3cb17075b929ec1 Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Thu, 11 Apr 2024 10:26:01 +0100 Subject: [PATCH 088/190] homing_override: Adds rawparams support Signed-off-by: Pedro Lamas --- klippy/extras/homing_override.py | 1 + 1 file changed, 1 insertion(+) diff --git a/klippy/extras/homing_override.py b/klippy/extras/homing_override.py index a1cf94b59db8..e498c8b8de05 100644 --- a/klippy/extras/homing_override.py +++ b/klippy/extras/homing_override.py @@ -55,6 +55,7 @@ def cmd_G28(self, gcmd): # Perform homing context = self.template.create_template_context() context['params'] = gcmd.get_command_parameters() + context['rawparams'] = gcmd.get_raw_command_parameters() try: self.in_script = True self.template.run_gcode_from_command(context) From 75d7c17656883484d43d15080cfd0441d2b8e322 Mon Sep 17 00:00:00 2001 From: trofen <39155883+trofen@users.noreply.github.com> Date: Thu, 11 Apr 2024 17:58:47 +0300 Subject: [PATCH 089/190] docs: Fix typo in Resonance_Compensation.md Signed-off-by: Plynskiy Nikita --- docs/Resonance_Compensation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Resonance_Compensation.md b/docs/Resonance_Compensation.md index 31f1b35e3b05..8f2d2b643c86 100644 --- a/docs/Resonance_Compensation.md +++ b/docs/Resonance_Compensation.md @@ -48,7 +48,7 @@ First, measure the **ringing frequency**. to 5.0. It is not advised to increase it when using input shaper because it can cause more smoothing in parts - it is better to use higher acceleration value instead. -2. Disable the `miminum_cruise_ratio` feature by issuing the following +2. Disable the `minimum_cruise_ratio` feature by issuing the following command: `SET_VELOCITY_LIMIT MINIMUM_CRUISE_RATIO=0` 3. Disable Pressure Advance: `SET_PRESSURE_ADVANCE ADVANCE=0` 4. If you have already added `[input_shaper]` section to the printer.cfg, From 36f9b26ef9ca7ff7d3c4887b7b1c3534252f5ea4 Mon Sep 17 00:00:00 2001 From: TheFeralEngineer <74030381+TheFeralEngineer@users.noreply.github.com> Date: Fri, 12 Apr 2024 12:27:36 -0400 Subject: [PATCH 090/190] config: Artillery Sidewinder X3 (#6534) Signed-off-by: Phil Timpson --- ...nter-artillery-sidewinder-x3-plus-2024.cfg | 188 ++++++++++++++++++ src/stm32/Kconfig | 2 +- test/klippy/printers.test | 1 + 3 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 config/printer-artillery-sidewinder-x3-plus-2024.cfg diff --git a/config/printer-artillery-sidewinder-x3-plus-2024.cfg b/config/printer-artillery-sidewinder-x3-plus-2024.cfg new file mode 100644 index 000000000000..eea1a980a8c0 --- /dev/null +++ b/config/printer-artillery-sidewinder-x3-plus-2024.cfg @@ -0,0 +1,188 @@ +# For the Artillery Sidewinder X3 Pro/Plus that came factory installed with V1.29 firmware, follow these steps. +# - Compile with the processor model STM32F401. +# - Select the 48KiB bootloader, +# - Select USB PA11/PA12 for USB communication interface. +# - Select USART2 PA3/PA2 for UART communication via the Wi-Fi Tx/Rx pins +# To set 48KiB bootloader, you need to make a change to make menuconfig Kconfig file +# Here is a link to a how-to video: https://youtu.be/dpc76zN7Dh0 +# Rename klipper.bin to yuntu.bin +# Copy the file out/yuntu.bin to an SD card and then restart the printer with that SD card +# +# For models that did not come with V1.29 installed +# - Compile with the processor model STM32F401. +# - Select the NO BOOTLOADER +# - Select USB PA11/PA12 for USB communication interface. +# - Select USART2 PA3/PA2 for UART communication via the Wi-Fi Tx/Rx pins +# - quit, save, make +# - Connect your printer to a computer running Pronterface, Octoprint, Repetier, BedLeveler5000 (anything with Console capability) +# - Power on the machine and send M997 through console into Marlin, this will put the board into "DFU" mode +# - DO NOT TURN OFF THE PRINTER +# - Connect your Linux/Klipper device to the USB port +# - Run lsusb and verify that the STM32 DFU device is visible (Bus 001 Device 006: ID 0483:df11 STMicroelectronics STM Device in DFU Mode) +# - Run sudo make flash 0483:df11 +# - Run lsusb again and there should be two devices: +# Bus 001 Device 007: ID 1d50:614e OpenMoko, Inc. stm32f401xc +# Bus 001 Device 003: ID 0cf3:e010 Qualcomm Atheros Communications stm32f401xc +# See docs/Config_Reference.md for a description of parameters. + +[mcu] +serial: /dev/ttyACM0 +restart_method: command + +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 15 +max_z_accel: 100 +square_corner_velocity: 5 + +[led LED_Light] +white_pin: PC2 +initial_white: 1.0 + +[neopixel hotend_neopixel] +pin: PD2 +color_order: GRB +initial_RED: 1.0 +initial_GREEN: 1.0 +initial_BLUE: 1.0 + +[stepper_x] +step_pin: PA8 +dir_pin: PC9 +enable_pin: !PA15 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PB9 +position_min: 0 +position_endstop: 0 +position_max: 315 +homing_speed: 50 + +[stepper_y] +step_pin: PC7 +dir_pin: !PC6 +enable_pin: !PC8 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PB8 +position_endstop: 0 +position_max: 315 +homing_speed: 50 + +[stepper_z] +step_pin: PB10 +dir_pin: !PA4 +enable_pin: !PC4 +rotation_distance: 8 +microsteps: 16 +position_min: -1 +position_max: 400 +endstop_pin: probe:z_virtual_endstop # Use Z- as endstop +#homing_speed: 10.0 + +[extruder] +max_extrude_only_distance: 100.0 +step_pin: PC11 +dir_pin: !PC10 +enable_pin: !PC12 +microsteps: 64 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PA6 +sensor_type: EPCOS 100K B57560G104F #Generic 3950 +sensor_pin: PC5 +min_extrude_temp: 170 +min_temp: 0 +max_temp: 300 +# Calibrate E-Steps https://www.klipper3d.org/Rotation_Distance.html#calibrating-rotation_distance-on-extruders +rotation_distance: 17.75 +# Calibrate PID: https://www.klipper3d.org/Config_checks.html#calibrate-pid-settings +# - Example: PID_CALIBRATE HEATER=extruder TARGET=200 +control: pid +pid_kp: 30.356 +pid_ki: 1.857 +pid_kd: 124.081 +# Calibrate PA: https://www.klipper3d.org/Pressure_Advance.html + +[heater_bed] +heater_pin: PA7 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC0 +max_temp: 100 +min_temp: 0 +# Calibrate PID: https://www.klipper3d.org/Config_checks.html#calibrate-pid-settings +# - Example: PID_CALIBRATE HEATER=heater_bed TARGET=60 +control: pid +pid_kp: 64.230 +pid_ki: 0.723 +pid_kd: 1425.905 + +[heater_fan hotend_fan] +pin: PB1 +heater: extruder +heater_temp: 50.0 + +[fan] +pin: PB0 + +[temperature_fan Artillery_MCU] +sensor_type: temperature_mcu +pin: PA5 +max_temp: 60.0 +target_temp: 40.0 +min_temp: 0 +shutdown_speed: 0.0 +kick_start_time: 0.5 +off_below: 0.19 +max_speed: 1.0 +min_speed: 0.0 +control: watermark + +[filament_switch_sensor filament_sensor] +pause_on_runout: true +switch_pin: PC1 + +[probe] +pin: PC14 +x_offset:45.2 +y_offset:11.6 +speed:5 +lift_speed:15 +z_offset: 2.350 + +[safe_z_home] +home_xy_position: 110, 145 # X, Y coordinate (e.g. 100, 100) where the Z homing should be +speed: 300.0 +z_hop: 10 +z_hop_speed: 15.0 + +[bed_mesh] +speed: 300 +horizontal_move_z: 6 +mesh_min: 46,15 +mesh_max: 300,300 +probe_count: 10, 10 +fade_start: 1.0 +fade_end: 0.0 +algorithm: bicubic + +[screws_tilt_adjust] +screw1: 120, 153 +screw1_name: center reference +screw2: 7, 45 +screw2_name: front left +screw3: 210, 45 +screw3_name: front right +screw4: 227, 145 +screw4_name: right center +screw5: 210, 245 +screw5_name: rear right +screw6: 7, 245 +screw6_name: rear left +screw7: 7, 145 +screw7_name: left center +horizontal_move_z: 8 +speed: 300 +screw_thread: CW-M4 diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index 2ae90bee824d..d14622a25d41 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -288,7 +288,7 @@ choice config STM32_FLASH_START_9000 bool "36KiB bootloader" if MACH_STM32F1 config STM32_FLASH_START_C000 - bool "48KiB bootloader" if MACH_STM32F4x5 + bool "48KiB bootloader" if MACH_STM32F4x5 || MACH_STM32F401 config STM32_FLASH_START_10000 bool "64KiB bootloader" if MACH_STM32F103 || MACH_STM32F4 diff --git a/test/klippy/printers.test b/test/klippy/printers.test index 94fe92c2724c..e404f6f153d0 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -205,6 +205,7 @@ CONFIG ../../config/printer-voxelab-aquila-2021.cfg DICTIONARY stm32f401.dict CONFIG ../../config/generic-fysetc-cheetah-v2.0.cfg CONFIG ../../config/printer-artillery-sidewinder-x2-2022.cfg +CONFIG ../../config/printer-artillery-sidewinder-x3-plus-2024.cfg CONFIG ../../config/printer-creality-ender5-s1-2023.cfg CONFIG ../../config/printer-elegoo-neptune3-pro-2023.cfg From 2425a74638baa87efef3ca02253804d126101c8a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 21 Mar 2024 23:22:10 -0400 Subject: [PATCH 091/190] virtual_sdcard: Define a default for on_gcode_error If on_gcode_error is not specified, default to running the TURN_OFF_HEATERS command. Signed-off-by: Kevin O'Connor --- docs/Config_Changes.md | 6 ++++++ docs/Config_Reference.md | 3 ++- klippy/extras/virtual_sdcard.py | 8 +++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index 668732f994c5..b5212ce19930 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,12 @@ All dates in this document are approximate. ## Changes +20240415: The `on_error_gcode` parameter in the `[virtual_sdcard]` +config section now has a default. If this parameter is not specified +it now defaults to `TURN_OFF_HEATERS`. If the previous behavior is +desired (take no default action on an error during a virtual_sdcard +print) then define `on_error_gcode` with an empty value. + 20240313: The `max_accel_to_decel` parameter in the `[printer]` config section has been deprecated. The `ACCEL_TO_DECEL` parameter of the `SET_VELOCITY_LIMIT` command has been deprecated. The diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 4999aba56bf7..403dfdfc4238 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -1472,7 +1472,8 @@ path: # be provided. #on_error_gcode: # A list of G-Code commands to execute when an error is reported. - +# See docs/Command_Templates.md for G-Code format. The default is to +# run TURN_OFF_HEATERS. ``` ### [sdcard_loop] diff --git a/klippy/extras/virtual_sdcard.py b/klippy/extras/virtual_sdcard.py index d49ebbcc41f4..6dc49e2f5c39 100644 --- a/klippy/extras/virtual_sdcard.py +++ b/klippy/extras/virtual_sdcard.py @@ -7,6 +7,12 @@ VALID_GCODE_EXTS = ['gcode', 'g', 'gco'] +DEFAULT_ERROR_GCODE = """ +{% if 'heaters' in printer %} + TURN_OFF_HEATERS +{% endif %} +""" + class VirtualSD: def __init__(self, config): self.printer = config.get_printer() @@ -27,7 +33,7 @@ def __init__(self, config): # Error handling gcode_macro = self.printer.load_object(config, 'gcode_macro') self.on_error_gcode = gcode_macro.load_template( - config, 'on_error_gcode', '') + config, 'on_error_gcode', DEFAULT_ERROR_GCODE) # Register commands self.gcode = self.printer.lookup_object('gcode') for cmd in ['M20', 'M21', 'M23', 'M24', 'M25', 'M26', 'M27']: From 12e9b633d89bf9bf6ad7c8b6198191159bcf27eb Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 12 Apr 2024 11:37:01 -0400 Subject: [PATCH 092/190] docs: Recommend using "ip" instead of "ifconfig" in CANBUS.md Some Linux systems do not install ifconfig, while ip should always be available. So, update the canbus documentation to recommend that. Signed-off-by: Kevin O'Connor --- docs/CANBUS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CANBUS.md b/docs/CANBUS.md index e80141a955dd..321f8e8914c6 100644 --- a/docs/CANBUS.md +++ b/docs/CANBUS.md @@ -31,7 +31,7 @@ adapter. This is typically done by creating a new file named allow-hotplug can0 iface can0 can static bitrate 1000000 - up ifconfig $IFACE txqueuelen 128 + up ip link set $IFACE txqueuelen 128 ``` ## Terminating Resistors @@ -113,7 +113,7 @@ Some important notes when using this mode: allow-hotplug can0 iface can0 can static bitrate 1000000 - up ifconfig $IFACE txqueuelen 128 + up ip link set $IFACE txqueuelen 128 ``` * The "bridge mcu" is not actually on the CAN bus. Messages to and From c106955850fde78f1eb1c68eb7c01a8aba67441f Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 12 Apr 2024 14:25:05 -0400 Subject: [PATCH 093/190] docs: Add information on txqueuelen to CANBUS_Troubleshooting.md Provide some background information on the Linux can interface txqueuelen parameter, errors that it can cause, and considerations when configuring it. Signed-off-by: Kevin O'Connor --- docs/CANBUS_Troubleshooting.md | 50 ++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docs/CANBUS_Troubleshooting.md b/docs/CANBUS_Troubleshooting.md index bd9ef0456205..1464c85ef37b 100644 --- a/docs/CANBUS_Troubleshooting.md +++ b/docs/CANBUS_Troubleshooting.md @@ -52,6 +52,56 @@ Reordered messages is a severe problem that must be fixed. It will result in unstable behavior and can lead to confusing errors at any part of a print. +## Use an appropriate txqueuelen setting + +The Klipper code uses the Linux kernel to manage CAN bus traffic. By +default, the kernel will only queue 10 CAN transmit packets. It is +recommended to [configure the can0 device](CANBUS.md#host-hardware) +with a `txqueuelen 128` to increase that size. + +If Klipper transmits a packet and Linux has filled all of its transmit +queue space then Linux will drop that packet and messages like the +following will appear in the Klipper log: +``` +Got error -1 in can write: (105)No buffer space available +``` +Klipper will automatically retransmit the lost messages as part of its +normal application level message retransmit system. Thus, this log +message is a warning and it does not indicate an unrecoverable error. + +If a complete CAN bus failure occurs (such as a CAN wire break) then +Linux will not be able to transmit any messages on the CAN bus and it +is common to find the above message in the Klipper log. In this case, +the log message is a symptom of a larger problem (the inability to +transmit any messages) and is not directly related to Linux +`txqueuelen`. + +One may check the current queue size by running the Linux command `ip +link show can0`. It should report a bunch of text including the +snippet `qlen 128`. If one sees something like `qlen 10` then it +indicates the CAN device has not been properly configured. + +It is not recommended to use a `txqueuelen` significantly larger than +128. A CAN bus running at a frequency of 1000000 will typically take +around 120us to transmit a CAN packet. Thus a queue of 128 packets is +likely to take around 15-20ms to drain. A substantially larger queue +could cause excessive spikes in message round-trip-time which could +lead to unrecoverable errors. Said another way, Klipper's application +retransmit system is more robust if it does not have to wait for Linux +to drain an excessively large queue of possibly stale data. This is +analogous to the problem of +[bufferbloat](https://en.wikipedia.org/wiki/Bufferbloat) on internet +routers. + +Under normal circumstances Klipper may utilize ~25 queue slots per +MCU - typically only utilizing more slots during retransmits. +(Specifically, the Klipper host may transmit up to 192 bytes to each +Klipper MCU before receiving an acknowledgment from that MCU.) If a +single CAN bus has 5 or more Klipper MCUs on it, then it might be +necessary to increase the `txqueuelen` above the recommended value +of 128. However, as above, care should be taken when selecting a new +value to avoid excessive round-trip-time latency. + ## Obtaining candump logs The CAN bus messages sent to and from the micro-controller are handled From 95fdb68587520e5a36449f58e2d9cf5459924d1c Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 13 Apr 2024 20:03:47 -0400 Subject: [PATCH 094/190] adxl345: Move sample timestamp calculation to reusable code Add a new extract_samples() method to the ChipClockUpdater class that calculates the sample timestamp for each sample in a list of bulk sensor reports. Update the adxl345 code to use that extract_samples() code. Signed-off-by: Kevin O'Connor --- klippy/extras/adxl345.py | 48 ++++++++++++++---------------------- klippy/extras/bulk_sensor.py | 27 +++++++++++++++++++- 2 files changed, 44 insertions(+), 31 deletions(-) diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index 738903f38a5d..623b8e504b5a 100644 --- a/klippy/extras/adxl345.py +++ b/klippy/extras/adxl345.py @@ -248,37 +248,25 @@ def start_internal_client(self): return aqh # Measurement decoding def _extract_samples(self, raw_samples): - # Load variables to optimize inner loop below + # Convert messages to samples + samples = self.clock_updater.extract_samples("BBBBB", raw_samples) + # Convert samples (x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map - last_sequence = self.clock_updater.get_last_sequence() - time_base, chip_base, inv_freq = self.clock_sync.get_time_translation() - # Process every message in raw_samples - count = seq = 0 - samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK) - for params in raw_samples: - seq_diff = (params['sequence'] - last_sequence) & 0xffff - seq_diff -= (seq_diff & 0x8000) << 1 - seq = last_sequence + seq_diff - d = bytearray(params['data']) - msg_cdiff = seq * SAMPLES_PER_BLOCK - chip_base - for i in range(len(d) // BYTES_PER_SAMPLE): - d_xyz = d[i*BYTES_PER_SAMPLE:(i+1)*BYTES_PER_SAMPLE] - xlow, ylow, zlow, xzhigh, yzhigh = d_xyz - if yzhigh & 0x80: - self.last_error_count += 1 - continue - rx = (xlow | ((xzhigh & 0x1f) << 8)) - ((xzhigh & 0x10) << 9) - ry = (ylow | ((yzhigh & 0x1f) << 8)) - ((yzhigh & 0x10) << 9) - rz = ((zlow | ((xzhigh & 0xe0) << 3) | ((yzhigh & 0xe0) << 6)) - - ((yzhigh & 0x40) << 7)) - raw_xyz = (rx, ry, rz) - x = round(raw_xyz[x_pos] * x_scale, 6) - y = round(raw_xyz[y_pos] * y_scale, 6) - z = round(raw_xyz[z_pos] * z_scale, 6) - ptime = round(time_base + (msg_cdiff + i) * inv_freq, 6) - samples[count] = (ptime, x, y, z) - count += 1 - self.clock_sync.set_last_chip_clock(seq * SAMPLES_PER_BLOCK + i) + count = 0 + for ptime, xlow, ylow, zlow, xzhigh, yzhigh in samples: + if yzhigh & 0x80: + self.last_error_count += 1 + continue + rx = (xlow | ((xzhigh & 0x1f) << 8)) - ((xzhigh & 0x10) << 9) + ry = (ylow | ((yzhigh & 0x1f) << 8)) - ((yzhigh & 0x10) << 9) + rz = ((zlow | ((xzhigh & 0xe0) << 3) | ((yzhigh & 0xe0) << 6)) + - ((yzhigh & 0x40) << 7)) + raw_xyz = (rx, ry, rz) + x = round(raw_xyz[x_pos] * x_scale, 6) + y = round(raw_xyz[y_pos] * y_scale, 6) + z = round(raw_xyz[z_pos] * z_scale, 6) + samples[count] = (round(ptime, 6), x, y, z) + count += 1 del samples[count:] return samples # Start, stop, and process message batches diff --git a/klippy/extras/bulk_sensor.py b/klippy/extras/bulk_sensor.py index ad486bc4b178..cb4c17ac67ce 100644 --- a/klippy/extras/bulk_sensor.py +++ b/klippy/extras/bulk_sensor.py @@ -3,7 +3,7 @@ # Copyright (C) 2020-2023 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import logging, threading +import logging, threading, struct # This "bulk sensor" module facilitates the processing of sensor chip # measurements that do not require the host to respond with low @@ -255,3 +255,28 @@ def update_clock(self, is_reset=False): self.clock_sync.reset(avg_mcu_clock, chip_clock) else: self.clock_sync.update(avg_mcu_clock, chip_clock) + # Convert a list of sensor_bulk_data responses into list of samples + def extract_samples(self, unpack_fmt, raw_samples): + unpack_from = struct.Struct(unpack_fmt).unpack_from + # Load variables to optimize inner loop below + last_sequence = self.get_last_sequence() + time_base, chip_base, inv_freq = self.clock_sync.get_time_translation() + bytes_per_sample = self.bytes_per_sample + samples_per_block = self.samples_per_block + # Process every message in raw_samples + count = seq = 0 + samples = [None] * (len(raw_samples) * samples_per_block) + for params in raw_samples: + seq_diff = (params['sequence'] - last_sequence) & 0xffff + seq_diff -= (seq_diff & 0x8000) << 1 + seq = last_sequence + seq_diff + msg_cdiff = seq * samples_per_block - chip_base + data = params['data'] + for i in range(len(data) // bytes_per_sample): + ptime = time_base + (msg_cdiff + i) * inv_freq + udata = unpack_from(data, i * bytes_per_sample) + samples[count] = (ptime,) + udata + count += 1 + self.clock_sync.set_last_chip_clock(seq * samples_per_block + i) + del samples[count:] + return samples From 144af05270f8862eada2881776d57eaa7ac454dc Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 13 Apr 2024 20:17:20 -0400 Subject: [PATCH 095/190] lis2dw: Use extract_samples() for sample timestamp calculation Signed-off-by: Kevin O'Connor --- klippy/extras/lis2dw.py | 44 +++++++++++------------------------------ 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/klippy/extras/lis2dw.py b/klippy/extras/lis2dw.py index e96313027561..a40e7056507f 100644 --- a/klippy/extras/lis2dw.py +++ b/klippy/extras/lis2dw.py @@ -93,40 +93,18 @@ def start_internal_client(self): return aqh # Measurement decoding def _extract_samples(self, raw_samples): - # Load variables to optimize inner loop below + # Convert messages to samples + samples = self.clock_updater.extract_samples(" Date: Sat, 13 Apr 2024 20:20:59 -0400 Subject: [PATCH 096/190] mpu9250: Use extract_samples() for sample timestamp calculation Signed-off-by: Kevin O'Connor --- klippy/extras/mpu9250.py | 41 +++++++++++----------------------------- 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/klippy/extras/mpu9250.py b/klippy/extras/mpu9250.py index b4b8c4391674..492131ae8988 100644 --- a/klippy/extras/mpu9250.py +++ b/klippy/extras/mpu9250.py @@ -110,37 +110,18 @@ def start_internal_client(self): return aqh # Measurement decoding def _extract_samples(self, raw_samples): - # Load variables to optimize inner loop below + # Convert messages to samples + samples = self.clock_updater.extract_samples(">hhh", raw_samples) + # Convert samples (x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map - last_sequence = self.clock_updater.get_last_sequence() - time_base, chip_base, inv_freq = self.clock_sync.get_time_translation() - # Process every message in raw_samples - count = seq = 0 - samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK) - for params in raw_samples: - seq_diff = (params['sequence'] - last_sequence) & 0xffff - seq_diff -= (seq_diff & 0x8000) << 1 - seq = last_sequence + seq_diff - d = bytearray(params['data']) - msg_cdiff = seq * SAMPLES_PER_BLOCK - chip_base - - for i in range(len(d) // BYTES_PER_SAMPLE): - d_xyz = d[i*BYTES_PER_SAMPLE:(i+1)*BYTES_PER_SAMPLE] - xhigh, xlow, yhigh, ylow, zhigh, zlow = d_xyz - # Merge and perform twos-complement - rx = ((xhigh << 8) | xlow) - ((xhigh & 0x80) << 9) - ry = ((yhigh << 8) | ylow) - ((yhigh & 0x80) << 9) - rz = ((zhigh << 8) | zlow) - ((zhigh & 0x80) << 9) - - raw_xyz = (rx, ry, rz) - x = round(raw_xyz[x_pos] * x_scale, 6) - y = round(raw_xyz[y_pos] * y_scale, 6) - z = round(raw_xyz[z_pos] * z_scale, 6) - ptime = round(time_base + (msg_cdiff + i) * inv_freq, 6) - samples[count] = (ptime, x, y, z) - count += 1 - self.clock_sync.set_last_chip_clock(seq * SAMPLES_PER_BLOCK + i) - del samples[count:] + count = 0 + for ptime, rx, ry, rz in samples: + raw_xyz = (rx, ry, rz) + x = round(raw_xyz[x_pos] * x_scale, 6) + y = round(raw_xyz[y_pos] * y_scale, 6) + z = round(raw_xyz[z_pos] * z_scale, 6) + samples[count] = (round(ptime, 6), x, y, z) + count += 1 return samples # Start, stop, and process message batches def _start_measurements(self): From 56829b07d2428abf199bac55cec499286eab5c6e Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 13 Apr 2024 20:26:16 -0400 Subject: [PATCH 097/190] ldc1612: Use extract_samples() for sample timestamp calculation Signed-off-by: Kevin O'Connor --- klippy/extras/ldc1612.py | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/klippy/extras/ldc1612.py b/klippy/extras/ldc1612.py index 16b8cd47ebab..e2f502d1e7d6 100644 --- a/klippy/extras/ldc1612.py +++ b/klippy/extras/ldc1612.py @@ -149,29 +149,17 @@ def clear_home(self): return self.mcu.clock_to_print_time(tclock) # Measurement decoding def _extract_samples(self, raw_samples): - # Load variables to optimize inner loop below - last_sequence = self.clock_updater.get_last_sequence() - time_base, chip_base, inv_freq = self.clock_sync.get_time_translation() + # Convert messages to samples + samples = self.clock_updater.extract_samples(">I", raw_samples) + # Convert samples freq_conv = float(LDC1612_FREQ) / (1<<28) - # Process every message in raw_samples - count = seq = 0 - samples = [None] * (len(raw_samples) * SAMPLES_PER_BLOCK) - for params in raw_samples: - seq_diff = (params['sequence'] - last_sequence) & 0xffff - seq_diff -= (seq_diff & 0x8000) << 1 - seq = last_sequence + seq_diff - d = bytearray(params['data']) - msg_cdiff = seq * SAMPLES_PER_BLOCK - chip_base - for i in range(len(d) // BYTES_PER_SAMPLE): - v = d[i*BYTES_PER_SAMPLE:(i+1)*BYTES_PER_SAMPLE] - if v[0] & 0xf0: - self.last_error_count += 1 - val = ((v[0] & 0x0f) << 24) | (v[1] << 16) | (v[2] << 8) | v[3] - ptime = round(time_base + (msg_cdiff + i) * inv_freq, 6) - samples[count] = (ptime, round(freq_conv * val, 3), 999.9) - count += 1 - self.clock_sync.set_last_chip_clock(seq * SAMPLES_PER_BLOCK + i) - del samples[count:] + count = 0 + for ptime, val in samples: + mv = val & 0x0fffffff + if mv != val: + self.last_error_count += 1 + samples[count] = (round(ptime, 6), round(freq_conv * mv, 3), 999.9) + count += 1 return samples # Start, stop, and process message batches def _start_measurements(self): From 9ceaae3847b4bb87f0e894d6133995f7a07b21bf Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 13 Apr 2024 21:02:04 -0400 Subject: [PATCH 098/190] bulk_sensor: Refactor ChipClockUpdater constructor Build the clock_sync and struct.Struct() in the ChipClockUpdater constructor. Signed-off-by: Kevin O'Connor --- klippy/extras/adxl345.py | 14 +++++--------- klippy/extras/bulk_sensor.py | 20 +++++++++++--------- klippy/extras/ldc1612.py | 14 +++++--------- klippy/extras/lis2dw.py | 14 +++++--------- klippy/extras/mpu9250.py | 14 +++++--------- 5 files changed, 31 insertions(+), 45 deletions(-) diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index 623b8e504b5a..079738942c7e 100644 --- a/klippy/extras/adxl345.py +++ b/klippy/extras/adxl345.py @@ -184,9 +184,6 @@ def read_axes_map(config): raise config.error("Invalid axes_map parameter") return [am[a.strip()] for a in axes_map] -BYTES_PER_SAMPLE = 5 -SAMPLES_PER_BLOCK = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE - BATCH_UPDATES = 0.100 # Printer class that controls ADXL345 chip @@ -211,9 +208,8 @@ def __init__(self, config): self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=oid) # Clock tracking chip_smooth = self.data_rate * BATCH_UPDATES * 2 - self.clock_sync = bulk_sensor.ClockSyncRegression(mcu, chip_smooth) - self.clock_updater = bulk_sensor.ChipClockUpdater(self.clock_sync, - BYTES_PER_SAMPLE) + self.clock_updater = bulk_sensor.ChipClockUpdater(mcu, chip_smooth, + "BBBBB") self.last_error_count = 0 # Process messages in batches self.batch_bulk = bulk_sensor.BatchBulkHelper( @@ -227,8 +223,8 @@ def _build_config(self): cmdqueue = self.spi.get_command_queue() self.query_adxl345_cmd = self.mcu.lookup_command( "query_adxl345 oid=%c rest_ticks=%u", cq=cmdqueue) - self.clock_updater.setup_query_command( - self.mcu, "query_adxl345_status oid=%c", oid=self.oid, cq=cmdqueue) + self.clock_updater.setup_query_command("query_adxl345_status oid=%c", + oid=self.oid, cq=cmdqueue) def read_reg(self, reg): params = self.spi.spi_transfer([reg | REG_MOD_READ, 0x00]) response = bytearray(params['response']) @@ -249,7 +245,7 @@ def start_internal_client(self): # Measurement decoding def _extract_samples(self, raw_samples): # Convert messages to samples - samples = self.clock_updater.extract_samples("BBBBB", raw_samples) + samples = self.clock_updater.extract_samples(raw_samples) # Convert samples (x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map count = 0 diff --git a/klippy/extras/bulk_sensor.py b/klippy/extras/bulk_sensor.py index cb4c17ac67ce..d514bc916aa7 100644 --- a/klippy/extras/bulk_sensor.py +++ b/klippy/extras/bulk_sensor.py @@ -202,15 +202,17 @@ def get_time_translation(self): # Handle common periodic chip status query responses class ChipClockUpdater: - def __init__(self, clock_sync, bytes_per_sample): - self.clock_sync = clock_sync - self.bytes_per_sample = bytes_per_sample - self.samples_per_block = MAX_BULK_MSG_SIZE // bytes_per_sample + def __init__(self, mcu, chip_clock_smooth, unpack_fmt): + self.mcu = mcu + self.clock_sync = ClockSyncRegression(mcu, chip_clock_smooth) + unpack = struct.Struct(unpack_fmt) + self.unpack_from = unpack.unpack_from + self.bytes_per_sample = unpack.size + self.samples_per_block = MAX_BULK_MSG_SIZE // self.bytes_per_sample self.last_sequence = self.max_query_duration = 0 self.last_overflows = 0 - self.mcu = self.oid = self.query_status_cmd = None - def setup_query_command(self, mcu, msgformat, oid, cq): - self.mcu = mcu + self.oid = self.query_status_cmd = None + def setup_query_command(self, msgformat, oid, cq): self.oid = oid self.query_status_cmd = self.mcu.lookup_query_command( msgformat, "sensor_bulk_status oid=%c clock=%u query_ticks=%u" @@ -256,11 +258,11 @@ def update_clock(self, is_reset=False): else: self.clock_sync.update(avg_mcu_clock, chip_clock) # Convert a list of sensor_bulk_data responses into list of samples - def extract_samples(self, unpack_fmt, raw_samples): - unpack_from = struct.Struct(unpack_fmt).unpack_from + def extract_samples(self, raw_samples): # Load variables to optimize inner loop below last_sequence = self.get_last_sequence() time_base, chip_base, inv_freq = self.clock_sync.get_time_translation() + unpack_from = self.unpack_from bytes_per_sample = self.bytes_per_sample samples_per_block = self.samples_per_block # Process every message in raw_samples diff --git a/klippy/extras/ldc1612.py b/klippy/extras/ldc1612.py index e2f502d1e7d6..4b8a3f72f9f3 100644 --- a/klippy/extras/ldc1612.py +++ b/klippy/extras/ldc1612.py @@ -10,9 +10,6 @@ BATCH_UPDATES = 0.100 -BYTES_PER_SAMPLE = 4 -SAMPLES_PER_BLOCK = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE - LDC1612_ADDR = 0x2a LDC1612_FREQ = 12000000 @@ -98,9 +95,8 @@ def __init__(self, config, calibration=None): self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=oid) # Clock tracking chip_smooth = self.data_rate * BATCH_UPDATES * 2 - self.clock_sync = bulk_sensor.ClockSyncRegression(mcu, chip_smooth) - self.clock_updater = bulk_sensor.ChipClockUpdater(self.clock_sync, - BYTES_PER_SAMPLE) + self.clock_updater = bulk_sensor.ChipClockUpdater(mcu, chip_smooth, + ">I") self.last_error_count = 0 # Process messages in batches self.batch_bulk = bulk_sensor.BatchBulkHelper( @@ -114,8 +110,8 @@ def _build_config(self): cmdqueue = self.i2c.get_command_queue() self.query_ldc1612_cmd = self.mcu.lookup_command( "query_ldc1612 oid=%c rest_ticks=%u", cq=cmdqueue) - self.clock_updater.setup_query_command( - self.mcu, "query_ldc1612_status oid=%c", oid=self.oid, cq=cmdqueue) + self.clock_updater.setup_query_command("query_ldc1612_status oid=%c", + oid=self.oid, cq=cmdqueue) self.ldc1612_setup_home_cmd = self.mcu.lookup_command( "ldc1612_setup_home oid=%c clock=%u threshold=%u" " trsync_oid=%c trigger_reason=%c", cq=cmdqueue) @@ -150,7 +146,7 @@ def clear_home(self): # Measurement decoding def _extract_samples(self, raw_samples): # Convert messages to samples - samples = self.clock_updater.extract_samples(">I", raw_samples) + samples = self.clock_updater.extract_samples(raw_samples) # Convert samples freq_conv = float(LDC1612_FREQ) / (1<<28) count = 0 diff --git a/klippy/extras/lis2dw.py b/klippy/extras/lis2dw.py index a40e7056507f..96b7db7644eb 100644 --- a/klippy/extras/lis2dw.py +++ b/klippy/extras/lis2dw.py @@ -30,9 +30,6 @@ FREEFALL_ACCEL = 9.80665 SCALE = FREEFALL_ACCEL * 1.952 / 4 -BYTES_PER_SAMPLE = 6 -SAMPLES_PER_BLOCK = bulk_sensor.MAX_BULK_MSG_SIZE // BYTES_PER_SAMPLE - BATCH_UPDATES = 0.100 # Printer class that controls LIS2DW chip @@ -55,9 +52,8 @@ def __init__(self, config): self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=oid) # Clock tracking chip_smooth = self.data_rate * BATCH_UPDATES * 2 - self.clock_sync = bulk_sensor.ClockSyncRegression(mcu, chip_smooth) - self.clock_updater = bulk_sensor.ChipClockUpdater(self.clock_sync, - BYTES_PER_SAMPLE) + self.clock_updater = bulk_sensor.ChipClockUpdater(mcu, chip_smooth, + "hhh") self.last_error_count = 0 # Process messages in batches self.batch_bulk = bulk_sensor.BatchBulkHelper( @@ -97,8 +93,8 @@ def _build_config(self): % (self.oid,), on_restart=True) self.query_mpu9250_cmd = self.mcu.lookup_command( "query_mpu9250 oid=%c rest_ticks=%u", cq=cmdqueue) - self.clock_updater.setup_query_command( - self.mcu, "query_mpu9250_status oid=%c", oid=self.oid, cq=cmdqueue) + self.clock_updater.setup_query_command("query_mpu9250_status oid=%c", + oid=self.oid, cq=cmdqueue) def read_reg(self, reg): params = self.i2c.i2c_read([reg], 1) return bytearray(params['response'])[0] @@ -111,7 +107,7 @@ def start_internal_client(self): # Measurement decoding def _extract_samples(self, raw_samples): # Convert messages to samples - samples = self.clock_updater.extract_samples(">hhh", raw_samples) + samples = self.clock_updater.extract_samples(raw_samples) # Convert samples (x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map count = 0 From abb79103162af2b7437c7a82f1c88d7fb1abfb68 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 14 Apr 2024 12:33:22 -0400 Subject: [PATCH 099/190] bulk_sensor: Rework ChipClockUpdater class into FixedFreqReader Move the sensor_bulk_data message queuing into the class, and then rename that class. This simplifies the users of the code. Signed-off-by: Kevin O'Connor --- klippy/extras/adxl345.py | 30 +++++++++------------------ klippy/extras/bulk_sensor.py | 39 ++++++++++++++++++++++++------------ klippy/extras/ldc1612.py | 30 +++++++++------------------ klippy/extras/lis2dw.py | 30 +++++++++------------------ klippy/extras/mpu9250.py | 30 +++++++++------------------ 5 files changed, 66 insertions(+), 93 deletions(-) diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index 079738942c7e..5323be797bd4 100644 --- a/klippy/extras/adxl345.py +++ b/klippy/extras/adxl345.py @@ -205,11 +205,9 @@ def __init__(self, config): mcu.add_config_cmd("query_adxl345 oid=%d rest_ticks=0" % (oid,), on_restart=True) mcu.register_config_callback(self._build_config) - self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=oid) - # Clock tracking + # Bulk sample message reading chip_smooth = self.data_rate * BATCH_UPDATES * 2 - self.clock_updater = bulk_sensor.ChipClockUpdater(mcu, chip_smooth, - "BBBBB") + self.ffreader = bulk_sensor.FixedFreqReader(mcu, chip_smooth, "BBBBB") self.last_error_count = 0 # Process messages in batches self.batch_bulk = bulk_sensor.BatchBulkHelper( @@ -223,8 +221,8 @@ def _build_config(self): cmdqueue = self.spi.get_command_queue() self.query_adxl345_cmd = self.mcu.lookup_command( "query_adxl345 oid=%c rest_ticks=%u", cq=cmdqueue) - self.clock_updater.setup_query_command("query_adxl345_status oid=%c", - oid=self.oid, cq=cmdqueue) + self.ffreader.setup_query_command("query_adxl345_status oid=%c", + oid=self.oid, cq=cmdqueue) def read_reg(self, reg): params = self.spi.spi_transfer([reg | REG_MOD_READ, 0x00]) response = bytearray(params['response']) @@ -243,10 +241,7 @@ def start_internal_client(self): self.batch_bulk.add_client(aqh.handle_batch) return aqh # Measurement decoding - def _extract_samples(self, raw_samples): - # Convert messages to samples - samples = self.clock_updater.extract_samples(raw_samples) - # Convert samples + def _convert_samples(self, samples): (x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map count = 0 for ptime, xlow, ylow, zlow, xzhigh, yzhigh in samples: @@ -264,7 +259,6 @@ def _extract_samples(self, raw_samples): samples[count] = (round(ptime, 6), x, y, z) count += 1 del samples[count:] - return samples # Start, stop, and process message batches def _start_measurements(self): # In case of miswiring, testing ADXL345 device ID prevents treating @@ -283,30 +277,26 @@ def _start_measurements(self): self.set_reg(REG_BW_RATE, QUERY_RATES[self.data_rate]) self.set_reg(REG_FIFO_CTL, SET_FIFO_CTL) # Start bulk reading - self.bulk_queue.clear_samples() rest_ticks = self.mcu.seconds_to_clock(4. / self.data_rate) self.query_adxl345_cmd.send([self.oid, rest_ticks]) self.set_reg(REG_POWER_CTL, 0x08) logging.info("ADXL345 starting '%s' measurements", self.name) # Initialize clock tracking - self.clock_updater.note_start() + self.ffreader.note_start() self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading self.set_reg(REG_POWER_CTL, 0x00) self.query_adxl345_cmd.send_wait_ack([self.oid, 0]) - self.bulk_queue.clear_samples() + self.ffreader.note_end() logging.info("ADXL345 finished '%s' measurements", self.name) def _process_batch(self, eventtime): - self.clock_updater.update_clock() - raw_samples = self.bulk_queue.pull_samples() - if not raw_samples: - return {} - samples = self._extract_samples(raw_samples) + samples = self.ffreader.pull_samples() + self._convert_samples(samples) if not samples: return {} return {'data': samples, 'errors': self.last_error_count, - 'overflows': self.clock_updater.get_last_overflows()} + 'overflows': self.ffreader.get_last_overflows()} def load_config(config): return ADXL345(config) diff --git a/klippy/extras/bulk_sensor.py b/klippy/extras/bulk_sensor.py index d514bc916aa7..6a63447b4799 100644 --- a/klippy/extras/bulk_sensor.py +++ b/klippy/extras/bulk_sensor.py @@ -200,8 +200,9 @@ def get_time_translation(self): MAX_BULK_MSG_SIZE = 52 -# Handle common periodic chip status query responses -class ChipClockUpdater: +# Read sensor_bulk_data and calculate timestamps for devices that take +# samples at a fixed frequency (and produce fixed data size samples). +class FixedFreqReader: def __init__(self, mcu, chip_clock_smooth, unpack_fmt): self.mcu = mcu self.clock_sync = ClockSyncRegression(mcu, chip_clock_smooth) @@ -211,27 +212,33 @@ def __init__(self, mcu, chip_clock_smooth, unpack_fmt): self.samples_per_block = MAX_BULK_MSG_SIZE // self.bytes_per_sample self.last_sequence = self.max_query_duration = 0 self.last_overflows = 0 - self.oid = self.query_status_cmd = None + self.bulk_queue = self.oid = self.query_status_cmd = None def setup_query_command(self, msgformat, oid, cq): + # Lookup sensor query command (that responds with sensor_bulk_status) self.oid = oid self.query_status_cmd = self.mcu.lookup_query_command( msgformat, "sensor_bulk_status oid=%c clock=%u query_ticks=%u" " next_sequence=%hu buffered=%u possible_overflows=%hu", oid=oid, cq=cq) - def get_last_sequence(self): - return self.last_sequence + # Read sensor_bulk_data messages and store in a queue + self.bulk_queue = BulkDataQueue(self.mcu, oid=oid) def get_last_overflows(self): return self.last_overflows - def clear_duration_filter(self): + def _clear_duration_filter(self): self.max_query_duration = 1 << 31 def note_start(self): self.last_sequence = 0 self.last_overflows = 0 + # Clear local queue (clear any stale samples from previous session) + self.bulk_queue.clear_samples() # Set initial clock - self.clear_duration_filter() - self.update_clock(is_reset=True) - self.clear_duration_filter() - def update_clock(self, is_reset=False): + self._clear_duration_filter() + self._update_clock(is_reset=True) + self._clear_duration_filter() + def note_end(self): + # Clear local queue (free no longer needed memory) + self.bulk_queue.clear_samples() + def _update_clock(self, is_reset=False): params = self.query_status_cmd.send([self.oid]) mcu_clock = self.mcu.clock32_to_clock64(params['clock']) seq_diff = (params['next_sequence'] - self.last_sequence) & 0xffff @@ -257,10 +264,16 @@ def update_clock(self, is_reset=False): self.clock_sync.reset(avg_mcu_clock, chip_clock) else: self.clock_sync.update(avg_mcu_clock, chip_clock) - # Convert a list of sensor_bulk_data responses into list of samples - def extract_samples(self, raw_samples): + # Convert sensor_bulk_data responses into list of samples + def pull_samples(self): + # Query MCU for sample timing and update clock synchronization + self._update_clock() + # Pull sensor_bulk_data messages from local queue + raw_samples = self.bulk_queue.pull_samples() + if not raw_samples: + return [] # Load variables to optimize inner loop below - last_sequence = self.get_last_sequence() + last_sequence = self.last_sequence time_base, chip_base, inv_freq = self.clock_sync.get_time_translation() unpack_from = self.unpack_from bytes_per_sample = self.bytes_per_sample diff --git a/klippy/extras/ldc1612.py b/klippy/extras/ldc1612.py index 4b8a3f72f9f3..2ae4dd7d7907 100644 --- a/klippy/extras/ldc1612.py +++ b/klippy/extras/ldc1612.py @@ -92,11 +92,9 @@ def __init__(self, config, calibration=None): mcu.add_config_cmd("query_ldc1612 oid=%d rest_ticks=0" % (oid,), on_restart=True) mcu.register_config_callback(self._build_config) - self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=oid) - # Clock tracking + # Bulk sample message reading chip_smooth = self.data_rate * BATCH_UPDATES * 2 - self.clock_updater = bulk_sensor.ChipClockUpdater(mcu, chip_smooth, - ">I") + self.ffreader = bulk_sensor.FixedFreqReader(mcu, chip_smooth, ">I") self.last_error_count = 0 # Process messages in batches self.batch_bulk = bulk_sensor.BatchBulkHelper( @@ -110,8 +108,8 @@ def _build_config(self): cmdqueue = self.i2c.get_command_queue() self.query_ldc1612_cmd = self.mcu.lookup_command( "query_ldc1612 oid=%c rest_ticks=%u", cq=cmdqueue) - self.clock_updater.setup_query_command("query_ldc1612_status oid=%c", - oid=self.oid, cq=cmdqueue) + self.ffreader.setup_query_command("query_ldc1612_status oid=%c", + oid=self.oid, cq=cmdqueue) self.ldc1612_setup_home_cmd = self.mcu.lookup_command( "ldc1612_setup_home oid=%c clock=%u threshold=%u" " trsync_oid=%c trigger_reason=%c", cq=cmdqueue) @@ -144,10 +142,7 @@ def clear_home(self): tclock = self.mcu.clock32_to_clock64(params['trigger_clock']) return self.mcu.clock_to_print_time(tclock) # Measurement decoding - def _extract_samples(self, raw_samples): - # Convert messages to samples - samples = self.clock_updater.extract_samples(raw_samples) - # Convert samples + def _convert_samples(self, samples): freq_conv = float(LDC1612_FREQ) / (1<<28) count = 0 for ptime, val in samples: @@ -156,7 +151,6 @@ def _extract_samples(self, raw_samples): self.last_error_count += 1 samples[count] = (round(ptime, 6), round(freq_conv * mv, 3), 999.9) count += 1 - return samples # Start, stop, and process message batches def _start_measurements(self): # In case of miswiring, testing LDC1612 device ID prevents treating @@ -180,27 +174,23 @@ def _start_measurements(self): self.set_reg(REG_CONFIG, 0x001 | (1<<12) | (1<<10) | (1<<9)) self.set_reg(REG_DRIVE_CURRENT0, self.dccal.get_drive_current() << 11) # Start bulk reading - self.bulk_queue.clear_samples() rest_ticks = self.mcu.seconds_to_clock(0.5 / self.data_rate) self.query_ldc1612_cmd.send([self.oid, rest_ticks]) logging.info("LDC1612 starting '%s' measurements", self.name) # Initialize clock tracking - self.clock_updater.note_start() + self.ffreader.note_start() self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading self.query_ldc1612_cmd.send_wait_ack([self.oid, 0]) - self.bulk_queue.clear_samples() + self.ffreader.note_end() logging.info("LDC1612 finished '%s' measurements", self.name) def _process_batch(self, eventtime): - self.clock_updater.update_clock() - raw_samples = self.bulk_queue.pull_samples() - if not raw_samples: - return {} - samples = self._extract_samples(raw_samples) + samples = self.ffreader.pull_samples() + self._convert_samples(samples) if not samples: return {} if self.calibration is not None: self.calibration.apply_calibration(samples) return {'data': samples, 'errors': self.last_error_count, - 'overflows': self.clock_updater.get_last_overflows()} + 'overflows': self.ffreader.get_last_overflows()} diff --git a/klippy/extras/lis2dw.py b/klippy/extras/lis2dw.py index 96b7db7644eb..3f17c1f4cf28 100644 --- a/klippy/extras/lis2dw.py +++ b/klippy/extras/lis2dw.py @@ -49,11 +49,9 @@ def __init__(self, config): mcu.add_config_cmd("query_lis2dw oid=%d rest_ticks=0" % (oid,), on_restart=True) mcu.register_config_callback(self._build_config) - self.bulk_queue = bulk_sensor.BulkDataQueue(mcu, oid=oid) - # Clock tracking + # Bulk sample message reading chip_smooth = self.data_rate * BATCH_UPDATES * 2 - self.clock_updater = bulk_sensor.ChipClockUpdater(mcu, chip_smooth, - "hhh") + self.ffreader = bulk_sensor.FixedFreqReader(mcu, chip_smooth, ">hhh") self.last_error_count = 0 # Process messages in batches self.batch_bulk = bulk_sensor.BatchBulkHelper( @@ -93,8 +91,8 @@ def _build_config(self): % (self.oid,), on_restart=True) self.query_mpu9250_cmd = self.mcu.lookup_command( "query_mpu9250 oid=%c rest_ticks=%u", cq=cmdqueue) - self.clock_updater.setup_query_command("query_mpu9250_status oid=%c", - oid=self.oid, cq=cmdqueue) + self.ffreader.setup_query_command("query_mpu9250_status oid=%c", + oid=self.oid, cq=cmdqueue) def read_reg(self, reg): params = self.i2c.i2c_read([reg], 1) return bytearray(params['response'])[0] @@ -105,10 +103,7 @@ def start_internal_client(self): self.batch_bulk.add_client(aqh.handle_batch) return aqh # Measurement decoding - def _extract_samples(self, raw_samples): - # Convert messages to samples - samples = self.clock_updater.extract_samples(raw_samples) - # Convert samples + def _convert_samples(self, samples): (x_pos, x_scale), (y_pos, y_scale), (z_pos, z_scale) = self.axes_map count = 0 for ptime, rx, ry, rz in samples: @@ -118,7 +113,6 @@ def _extract_samples(self, raw_samples): z = round(raw_xyz[z_pos] * z_scale, 6) samples[count] = (round(ptime, 6), x, y, z) count += 1 - return samples # Start, stop, and process message batches def _start_measurements(self): # In case of miswiring, testing MPU9250 device ID prevents treating @@ -151,32 +145,28 @@ def _start_measurements(self): self.read_reg(REG_INT_STATUS) # clear FIFO overflow flag # Start bulk reading - self.bulk_queue.clear_samples() rest_ticks = self.mcu.seconds_to_clock(4. / self.data_rate) self.query_mpu9250_cmd.send([self.oid, rest_ticks]) self.set_reg(REG_FIFO_EN, SET_ENABLE_FIFO) logging.info("MPU9250 starting '%s' measurements", self.name) # Initialize clock tracking - self.clock_updater.note_start() + self.ffreader.note_start() self.last_error_count = 0 def _finish_measurements(self): # Halt bulk reading self.set_reg(REG_FIFO_EN, SET_DISABLE_FIFO) self.query_mpu9250_cmd.send_wait_ack([self.oid, 0]) - self.bulk_queue.clear_samples() + self.ffreader.note_end() logging.info("MPU9250 finished '%s' measurements", self.name) self.set_reg(REG_PWR_MGMT_1, SET_PWR_MGMT_1_SLEEP) self.set_reg(REG_PWR_MGMT_2, SET_PWR_MGMT_2_OFF) def _process_batch(self, eventtime): - self.clock_updater.update_clock() - raw_samples = self.bulk_queue.pull_samples() - if not raw_samples: - return {} - samples = self._extract_samples(raw_samples) + samples = self.ffreader.pull_samples() + self._convert_samples(samples) if not samples: return {} return {'data': samples, 'errors': self.last_error_count, - 'overflows': self.clock_updater.get_last_overflows()} + 'overflows': self.ffreader.get_last_overflows()} def load_config(config): return MPU9250(config) From 819599362cad73316759e003b048545d09258a54 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 14 Apr 2024 12:39:27 -0400 Subject: [PATCH 100/190] bulk_sensor: Rename BulkDataQueue methods Rename pull_samples() to pull_queue() and rename clear_sample() to clear_queue(). This avoids confusion between the queue of response messages and the larger list of samples stored within those messages. Signed-off-by: Kevin O'Connor --- klippy/extras/angle.py | 6 +++--- klippy/extras/bulk_sensor.py | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/klippy/extras/angle.py b/klippy/extras/angle.py index 23f402a7e0f8..c51d8bf0662d 100644 --- a/klippy/extras/angle.py +++ b/klippy/extras/angle.py @@ -529,7 +529,7 @@ def _start_measurements(self): logging.info("Starting angle '%s' measurements", self.name) self.sensor_helper.start() # Start bulk reading - self.bulk_queue.clear_samples() + self.bulk_queue.clear_queue() self.last_sequence = 0 systime = self.printer.get_reactor().monotonic() print_time = self.mcu.estimated_print_time(systime) + MIN_MSG_TIME @@ -541,13 +541,13 @@ def _start_measurements(self): def _finish_measurements(self): # Halt bulk reading self.query_spi_angle_cmd.send_wait_ack([self.oid, 0, 0, 0]) - self.bulk_queue.clear_samples() + self.bulk_queue.clear_queue() self.sensor_helper.last_temperature = None logging.info("Stopped angle '%s' measurements", self.name) def _process_batch(self, eventtime): if self.sensor_helper.is_tcode_absolute: self.sensor_helper.update_clock() - raw_samples = self.bulk_queue.pull_samples() + raw_samples = self.bulk_queue.pull_queue() if not raw_samples: return {} samples, error_count = self._extract_samples(raw_samples) diff --git a/klippy/extras/bulk_sensor.py b/klippy/extras/bulk_sensor.py index 6a63447b4799..1720c0522736 100644 --- a/klippy/extras/bulk_sensor.py +++ b/klippy/extras/bulk_sensor.py @@ -123,13 +123,13 @@ def __init__(self, mcu, msg_name="sensor_bulk_data", oid=None): def _handle_data(self, params): with self.lock: self.raw_samples.append(params) - def pull_samples(self): + def pull_queue(self): with self.lock: raw_samples = self.raw_samples self.raw_samples = [] return raw_samples - def clear_samples(self): - self.pull_samples() + def clear_queue(self): + self.pull_queue() ###################################################################### @@ -230,14 +230,14 @@ def note_start(self): self.last_sequence = 0 self.last_overflows = 0 # Clear local queue (clear any stale samples from previous session) - self.bulk_queue.clear_samples() + self.bulk_queue.clear_queue() # Set initial clock self._clear_duration_filter() self._update_clock(is_reset=True) self._clear_duration_filter() def note_end(self): # Clear local queue (free no longer needed memory) - self.bulk_queue.clear_samples() + self.bulk_queue.clear_queue() def _update_clock(self, is_reset=False): params = self.query_status_cmd.send([self.oid]) mcu_clock = self.mcu.clock32_to_clock64(params['clock']) @@ -269,7 +269,7 @@ def pull_samples(self): # Query MCU for sample timing and update clock synchronization self._update_clock() # Pull sensor_bulk_data messages from local queue - raw_samples = self.bulk_queue.pull_samples() + raw_samples = self.bulk_queue.pull_queue() if not raw_samples: return [] # Load variables to optimize inner loop below From 28faf814143a22534f2afa5b292ffd97b8acf886 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 20 Apr 2024 13:23:42 -0400 Subject: [PATCH 101/190] docs: Update CANBUS_Troubleshooting.md to avoid formatting error Avoid starting a line with "128." as that confused markdown. Signed-off-by: Kevin O'Connor --- docs/CANBUS_Troubleshooting.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/CANBUS_Troubleshooting.md b/docs/CANBUS_Troubleshooting.md index 1464c85ef37b..de0deaf74e06 100644 --- a/docs/CANBUS_Troubleshooting.md +++ b/docs/CANBUS_Troubleshooting.md @@ -81,15 +81,15 @@ link show can0`. It should report a bunch of text including the snippet `qlen 128`. If one sees something like `qlen 10` then it indicates the CAN device has not been properly configured. -It is not recommended to use a `txqueuelen` significantly larger than -128. A CAN bus running at a frequency of 1000000 will typically take -around 120us to transmit a CAN packet. Thus a queue of 128 packets is -likely to take around 15-20ms to drain. A substantially larger queue -could cause excessive spikes in message round-trip-time which could -lead to unrecoverable errors. Said another way, Klipper's application -retransmit system is more robust if it does not have to wait for Linux -to drain an excessively large queue of possibly stale data. This is -analogous to the problem of +It is not recommended to use a `txqueuelen` significantly larger +than 128. A CAN bus running at a frequency of 1000000 will typically +take around 120us to transmit a CAN packet. Thus a queue of 128 +packets is likely to take around 15-20ms to drain. A substantially +larger queue could cause excessive spikes in message round-trip-time +which could lead to unrecoverable errors. Said another way, Klipper's +application retransmit system is more robust if it does not have to +wait for Linux to drain an excessively large queue of possibly stale +data. This is analogous to the problem of [bufferbloat](https://en.wikipedia.org/wiki/Bufferbloat) on internet routers. From 713b50969848910d52c7d91dce695140bb59df9d Mon Sep 17 00:00:00 2001 From: Timofey Titovets Date: Sun, 21 Apr 2024 00:42:31 +0200 Subject: [PATCH 102/190] sht3x: Add sht31 support (#6560) Signed-off-by: Timofey Titovets --- docs/Config_Reference.md | 19 +++ docs/Status_Reference.md | 3 +- klippy/extras/sht3x.py | 165 ++++++++++++++++++++++++++ klippy/extras/temperature_sensors.cfg | 2 + 4 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 klippy/extras/sht3x.py diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 403dfdfc4238..71cdfed872f1 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -2575,6 +2575,25 @@ sensor_type: # Interval in seconds between readings. Default is 30 ``` +### SHT3X sensor + +SHT3X family two wire interface (I2C) environmental sensor. These sensors +have a range of -55~125 C, so are usable for e.g. chamber temperature +monitoring. They can also function as simple fan/heater controllers. + +``` +sensor_type: SHT3X +#i2c_address: +# Default is 68 (0x44). +#i2c_mcu: +#i2c_bus: +#i2c_software_scl_pin: +#i2c_software_sda_pin: +#i2c_speed: +# See the "common I2C settings" section for a description of the +# above parameters. +``` + ### LM75 temperature sensor LM75/LM75A two wire (I2C) connected temperature sensors. These sensors diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index 0e72a12b1f9f..66d840d16666 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -445,6 +445,7 @@ The following information is available in [bme280 config_section_name](Config_Reference.md#bmp280bme280bme680-temperature-sensor), [htu21d config_section_name](Config_Reference.md#htu21d-sensor), +[sht3x config_section_name](Config_Reference.md#sht31-sensor), [lm75 config_section_name](Config_Reference.md#lm75-temperature-sensor), [temperature_host config_section_name](Config_Reference.md#host-temperature-sensor) and @@ -452,7 +453,7 @@ and objects: - `temperature`: The last read temperature from the sensor. - `humidity`, `pressure`, `gas`: The last read values from the sensor - (only on bme280, htu21d, and lm75 sensors). + (only on bme280, htu21d, sht3x and lm75 sensors). ## temperature_fan diff --git a/klippy/extras/sht3x.py b/klippy/extras/sht3x.py new file mode 100644 index 000000000000..699d3f2095f4 --- /dev/null +++ b/klippy/extras/sht3x.py @@ -0,0 +1,165 @@ +# SHT3X i2c based temperature sensors support +# +# Copyright (C) 2024 Timofey Titovets +# Based on htu21d.py code +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import logging +from . import bus + +###################################################################### +# Compatible Sensors: +# SHT31 - Tested on octopus pro and Linux MCU +# +###################################################################### +SHT3X_I2C_ADDR = 0x44 + +SHT3X_CMD = { + 'MEASURE': { + 'STRETCH_ENABLED': { + 'HIGH_REP': [0x2c, 0x06], # High (15ms) repeatability measurement + 'MED_REP': [0x2c, 0x0D], # Medium (6ms) repeatability measurement + 'LOW_REP': [0x2c, 0x10], # Low (4ms) repeatability measurement + }, + 'STRETCH_DISABLED' : { + 'HIGH_REP': [0x24, 0x00], + 'MED_REP': [0x24, 0x0B], + 'LOW_REP': [0x24, 0x16], + }, + }, + 'OTHER': { + 'STATUS': { + 'READ': [0xF3, 0x2D], + 'CLEAN': [0x30, 0x41], + }, + 'SOFTRESET': [0x30, 0xA2], # Soft reset + 'HEATER': { + "ENABLE": [0x30, 0x6D], + "DISABLE": [0x30, 0x66], + }, + 'FETCH': [0xE0, 0x00], + 'BREAK': [0x30, 0x93], + } +} + +class SHT3X: + def __init__(self, config): + self.printer = config.get_printer() + self.name = config.get_name().split()[-1] + self.reactor = self.printer.get_reactor() + self.i2c = bus.MCU_I2C_from_config( + config, default_addr=SHT3X_I2C_ADDR, default_speed=100000) + self.report_time = config.getint('sht3x_report_time', 1, minval=1) + self.deviceId = config.get('sensor_type') + self.temp = self.min_temp = self.max_temp = self.humidity = 0. + self.sample_timer = self.reactor.register_timer(self._sample_sht3x) + self.printer.add_object("sht3x " + self.name, self) + self.printer.register_event_handler("klippy:connect", + self.handle_connect) + def handle_connect(self): + self._init_sht3x() + self.reactor.update_timer(self.sample_timer, self.reactor.NOW) + + def setup_minmax(self, min_temp, max_temp): + self.min_temp = min_temp + self.max_temp = max_temp + + def setup_callback(self, cb): + self._callback = cb + + def get_report_time_delta(self): + return self.report_time + + def _init_sht3x(self): + # Device Soft Reset + self.i2c.i2c_write(SHT3X_CMD['OTHER']['SOFTRESET']) + + # Wait 2ms after reset + self.reactor.pause(self.reactor.monotonic() + .02) + + status = self.i2c.i2c_read(SHT3X_CMD['OTHER']['STATUS']['READ'], 3) + response = bytearray(status['response']) + status = response[0] << 8 + status |= response[1] + checksum = response[2] + + if self._crc8(status) != checksum: + logging.warning("sht3x: Reading status - checksum error!") + + def _sample_sht3x(self, eventtime): + try: + # Read Temeprature + params = self.i2c.i2c_write( + SHT3X_CMD['MEASURE']['STRETCH_ENABLED']['HIGH_REP'] + ) + # Wait + self.reactor.pause(self.reactor.monotonic() + + .20) + + params = self.i2c.i2c_read([], 6) + + response = bytearray(params['response']) + rtemp = response[0] << 8 + rtemp |= response[1] + if self._crc8(rtemp) != response[2]: + logging.warning( + "sht3x: Checksum error on Temperature reading!" + ) + else: + self.temp = -45 + (175 * rtemp / 65535) + logging.debug("sht3x: Temperature %.2f " % self.temp) + + rhumid = response[3] << 8 + rhumid |= response[4] + if self._crc8(rhumid) != response[5]: + logging.warning("sht3x: Checksum error on Humidity reading!") + else: + self.humidity = 100 * rhumid / 65535 + logging.debug("sht3x: Humidity %.2f " % self.humidity) + + except Exception: + logging.exception("sht3x: Error reading data") + self.temp = self.humidity = .0 + return self.reactor.NEVER + + if self.temp < self.min_temp or self.temp > self.max_temp: + self.printer.invoke_shutdown( + "sht3x: temperature %0.1f outside range of %0.1f:%.01f" + % (self.temp, self.min_temp, self.max_temp)) + + measured_time = self.reactor.monotonic() + print_time = self.i2c.get_mcu().estimated_print_time(measured_time) + self._callback(print_time, self.temp) + return measured_time + self.report_time + + def _split_bytes(self, data): + bytes = [] + for i in range((data.bit_length() + 7) // 8): + bytes.append((data >> i*8) & 0xFF) + bytes.reverse() + return bytes + + def _crc8(self, data): + #crc8 polynomial for 16bit value, CRC8 -> x^8 + x^5 + x^4 + 1 + SHT3X_CRC8_POLYNOMINAL= 0x31 + crc = 0xFF + data_bytes = self._split_bytes(data) + for byte in data_bytes: + crc ^= byte + for _ in range(8): + if crc & 0x80: + crc = (crc << 1) ^ SHT3X_CRC8_POLYNOMINAL + else: + crc <<= 1 + return crc & 0xFF + + def get_status(self, eventtime): + return { + 'temperature': round(self.temp, 2), + 'humidity': round(self.humidity, 1), + } + +def load_config(config): + # Register sensor + pheater = config.get_printer().lookup_object("heaters") + pheater.add_sensor_factory("SHT3X", SHT3X) diff --git a/klippy/extras/temperature_sensors.cfg b/klippy/extras/temperature_sensors.cfg index 107fcd24b663..4fbe5492c709 100644 --- a/klippy/extras/temperature_sensors.cfg +++ b/klippy/extras/temperature_sensors.cfg @@ -18,6 +18,8 @@ # Load "SI7013", "SI7020", "SI7021", "SHT21", and "HTU21D" sensors [htu21d] +[sht3x] + # Load "AHT10" [aht10] From 2f6e94c94cae036b70b02df996dc12e2e61e5dcb Mon Sep 17 00:00:00 2001 From: Alessandro Maggi <59124971+DicyRoll@users.noreply.github.com> Date: Sun, 21 Apr 2024 00:57:58 +0200 Subject: [PATCH 103/190] docs: Fix typo in Bed_Mesh.md (#6572) Signed-off-by: Maggi Alessandro --- docs/Bed_Mesh.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/Bed_Mesh.md b/docs/Bed_Mesh.md index 9ee8df507e77..1538f6257210 100644 --- a/docs/Bed_Mesh.md +++ b/docs/Bed_Mesh.md @@ -44,10 +44,9 @@ probe_count: 5, 3 - `mesh_max: 240, 198`\ _Required_\ - The probed coordinate farthest farthest from the origin. This is not - necessarily the last point probed, as the probing process occurs in a - zig-zag fashion. As with `mesh_min`, this coordinate is relative to - the probe's location. + The probed coordinate farthest from the origin. This is not necessarily + the last point probed, as the probing process occurs in a zig-zag fashion. + As with `mesh_min`, this coordinate is relative to the probe's location. - `probe_count: 5, 3`\ _Default Value: 3, 3_\ From c3ec4af6cc3a62353e1eed57e0514356eeef1fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E7=8E=AE=20=28Jade=20Lin=29?= Date: Thu, 25 Apr 2024 09:45:05 +0800 Subject: [PATCH 104/190] bme280: Add BMP388 sensor support to BMxx80 (#6576) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the BMxx80 category with support for the BMP388 sensor, providing temperature and pressure output similar to the existing BMxx80 class of sensors. Signed-off-by: 林玮 (Jade Lin) --- docs/Config_Reference.md | 8 +- klippy/extras/bme280.py | 153 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 155 insertions(+), 6 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 71cdfed872f1..b66140552938 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -2490,9 +2490,9 @@ sensor_pin: # name in the above list. ``` -### BMP180/BMP280/BME280/BME680 temperature sensor +### BMP180/BMP280/BME280/BMP388/BME680 temperature sensor -BMP180/BMP280/BME280/BME680 two wire interface (I2C) environmental sensors. +BMP180/BMP280/BME280/BMP388/BME680 two wire interface (I2C) environmental sensors. Note that these sensors are not intended for use with extruders and heater beds, but rather for monitoring ambient temperature (C), pressure (hPa), relative humidity and in case of the BME680 gas level. @@ -2503,8 +2503,8 @@ temperature. ``` sensor_type: BME280 #i2c_address: -# Default is 118 (0x76). The BMP180 and some BME280 sensors have an address of 119 -# (0x77). +# Default is 118 (0x76). The BMP180, BMP388 and some BME280 sensors +# have an address of 119 (0x77). #i2c_mcu: #i2c_bus: #i2c_software_scl_pin: diff --git a/klippy/extras/bme280.py b/klippy/extras/bme280.py index 3bc3c471cb1b..262dc130f417 100644 --- a/klippy/extras/bme280.py +++ b/klippy/extras/bme280.py @@ -17,6 +17,29 @@ 'HUM_MSB': 0xFD, 'HUM_LSB': 0xFE, 'CAL_1': 0x88, 'CAL_2': 0xE1 } +BMP388_REGS = { + "CMD": 0x7E, + "STATUS": 0x03, + "PWR_CTRL": 0x1B, + "OSR": 0x1C, + "ORD": 0x1D, + "INT_CTRL": 0x19, + "CAL_1": 0x31, + "TEMP_MSB": 0x09, + "TEMP_LSB": 0x08, + "TEMP_XLSB": 0x07, + "PRESS_MSB": 0x06, + "PRESS_LSB": 0x05, + "PRESS_XLSB": 0x04, +} +BMP388_REG_VAL_PRESS_EN = 0x01 +BMP388_REG_VAL_TEMP_EN = 0x02 +BMP388_REG_VAL_PRESS_OS_NO = 0b000 +BMP388_REG_VAL_TEMP_OS_NO = 0b000000 +BMP388_REG_VAL_ODR_50_HZ = 0x02 +BMP388_REG_VAL_DRDY_EN = 0b100000 +BMP388_REG_VAL_NORMAL_MODE = 0x30 + BME680_REGS = { 'RESET': 0xE0, 'CTRL_HUM': 0x72, 'CTRL_GAS_1': 0x71, 'CTRL_GAS_0': 0x70, 'GAS_WAIT_0': 0x64, 'RES_HEAT_0': 0x5A, 'IDAC_HEAT_0': 0x50, @@ -68,9 +91,11 @@ RESET_CHIP_VALUE = 0xB6 BME_CHIPS = { - 0x58: 'BMP280', 0x60: 'BME280', 0x61: 'BME680', 0x55: 'BMP180' + 0x58: 'BMP280', 0x60: 'BME280', 0x61: 'BME680', 0x55: 'BMP180', + 0x50: 'BMP388' } BME_CHIP_ID_REG = 0xD0 +BMP3_CHIP_ID_REG = 0x00 def get_twos_complement(val, bit_size): @@ -163,6 +188,29 @@ def read_calibration_data_bmp280(calib_data_1): dig['P9'] = get_signed_short(calib_data_1[22:24]) return dig + def read_calibration_data_bmp388(calib_data_1): + dig = {} + dig["T1"] = get_unsigned_short(calib_data_1[0:2]) / 0.00390625 + dig["T2"] = get_unsigned_short(calib_data_1[2:4]) / 1073741824.0 + dig["T3"] = get_signed_byte(calib_data_1[4]) / 281474976710656.0 + + dig["P1"] = get_signed_short(calib_data_1[5:7]) - 16384 + dig["P1"] /= 1048576.0 + dig["P2"] = get_signed_short(calib_data_1[7:9]) - 16384 + dig["P2"] /= 536870912.0 + dig["P3"] = get_signed_byte(calib_data_1[9]) / 4294967296.0 + dig["P4"] = get_signed_byte(calib_data_1[10]) / 137438953472.0 + dig["P5"] = get_unsigned_short(calib_data_1[11:13]) / 0.125 + dig["P6"] = get_unsigned_short(calib_data_1[13:15]) / 64.0 + dig["P7"] = get_signed_byte(calib_data_1[15]) / 256.0 + dig["P8"] = get_signed_byte(calib_data_1[16]) / 32768.0 + dig["P9"] = get_signed_short(calib_data_1[17:19]) + dig["P9"] /= 281474976710656.0 + dig["P10"] = get_signed_byte(calib_data_1[19]) / 281474976710656.0 + dig["P11"] = get_signed_byte(calib_data_1[20]) + dig["P11"] /= 36893488147419103232.0 + return dig + def read_calibration_data_bme280(calib_data_1, calib_data_2): dig = read_calibration_data_bmp280(calib_data_1) dig['H1'] = calib_data_1[25] & 0xFF @@ -224,7 +272,7 @@ def read_calibration_data_bmp180(calib_data_1): dig['MD'] = get_signed_short_msb(calib_data_1[20:22]) return dig - chip_id = self.read_id() + chip_id = self.read_id() or self.read_bmp3_id() if chip_id not in BME_CHIPS.keys(): logging.info("bme280: Unknown Chip ID received %#x" % chip_id) else: @@ -252,6 +300,24 @@ def read_calibration_data_bmp180(calib_data_1): self.max_sample_time = (1.25 + ((2.3 * self.os_pres) + .575)) / 1000 self.sample_timer = self.reactor.register_timer(self._sample_bmp180) self.chip_registers = BMP180_REGS + elif self.chip_type == 'BMP388': + self.max_sample_time = 0.5 + self.chip_registers = BMP388_REGS + self.write_register( + "PWR_CTRL", + [ + BMP388_REG_VAL_PRESS_EN + | BMP388_REG_VAL_TEMP_EN + | BMP388_REG_VAL_NORMAL_MODE + ], + ) + self.write_register( + "OSR", [BMP388_REG_VAL_PRESS_OS_NO | BMP388_REG_VAL_TEMP_OS_NO] + ) + self.write_register("ORD", [BMP388_REG_VAL_ODR_50_HZ]) + self.write_register("INT_CTRL", [BMP388_REG_VAL_DRDY_EN]) + + self.sample_timer = self.reactor.register_timer(self._sample_bmp388) else: self.max_sample_time = \ (1.25 + (2.3 * self.os_temp) + ((2.3 * self.os_pres) + .575) @@ -265,6 +331,8 @@ def read_calibration_data_bmp180(calib_data_1): # Read out and calculate the trimming parameters if self.chip_type == 'BMP180': cal_1 = self.read_register('CAL_1', 22) + elif self.chip_type == 'BMP388': + cal_1 = self.read_register('CAL_1', 21) else: cal_1 = self.read_register('CAL_1', 26) cal_2 = self.read_register('CAL_2', 16) @@ -276,6 +344,8 @@ def read_calibration_data_bmp180(calib_data_1): self.dig = read_calibration_data_bme680(cal_1, cal_2) elif self.chip_type == 'BMP180': self.dig = read_calibration_data_bmp180(cal_1) + elif self.chip_type == 'BMP388': + self.dig = read_calibration_data_bmp388(cal_1) def _sample_bme280(self, eventtime): # Enter forced mode @@ -318,6 +388,79 @@ def _sample_bme280(self, eventtime): self._callback(self.mcu.estimated_print_time(measured_time), self.temp) return measured_time + REPORT_TIME + def _sample_bmp388(self, eventtime): + status = self.read_register("STATUS", 1) + if status[0] & 0b100000: + self.temp = self._sample_bmp388_temp() + if self.temp < self.min_temp or self.temp > self.max_temp: + self.printer.invoke_shutdown( + "BME280 temperature %0.1f outside range of %0.1f:%.01f" + % (self.temp, self.min_temp, self.max_temp) + ) + + if status[0] & 0b010000: + self.pressure = self._sample_bmp388_press() / 100.0 + + measured_time = self.reactor.monotonic() + self._callback(self.mcu.estimated_print_time(measured_time), self.temp) + return measured_time + REPORT_TIME + + def _sample_bmp388_temp(self): + xlsb = self.read_register("TEMP_XLSB", 1) + lsb = self.read_register("TEMP_LSB", 1) + msb = self.read_register("TEMP_MSB", 1) + adc_T = (msb[0] << 16) + (lsb[0] << 8) + (xlsb[0]) + + partial_data1 = adc_T - self.dig["T1"] + partial_data2 = self.dig["T2"] * partial_data1 + + self.t_fine = partial_data2 + self.t_fine += (partial_data1 * partial_data1) * self.dig["T3"] + + if self.t_fine < -40.0: + self.t_fine = -40.0 + + if self.t_fine > 85.0: + self.t_fine = 85.0 + + return self.t_fine + + def _sample_bmp388_press(self): + xlsb = self.read_register("PRESS_XLSB", 1) + lsb = self.read_register("PRESS_LSB", 1) + msb = self.read_register("PRESS_MSB", 1) + adc_P = (msb[0] << 16) + (lsb[0] << 8) + (xlsb[0]) + + partial_data1 = self.dig["P6"] * self.t_fine + partial_data2 = self.dig["P7"] * (self.t_fine * self.t_fine) + partial_data3 = self.dig["P8"] + partial_data3 *= self.t_fine * self.t_fine * self.t_fine + partial_out1 = self.dig["P5"] + partial_out1 += partial_data1 + partial_data2 + partial_data3 + + partial_data1 = self.dig["P2"] * self.t_fine + partial_data2 = self.dig["P3"] * (self.t_fine * self.t_fine) + partial_data3 = self.dig["P4"] + partial_data3 *= (self.t_fine * self.t_fine * self.t_fine) + partial_out2 = adc_P * ( + self.dig["P1"] + partial_data1 + partial_data2 + partial_data3 + ) + + partial_data1 = adc_P * adc_P + partial_data2 = self.dig["P9"] + (self.dig["P10"] * self.t_fine) + partial_data3 = partial_data1 * partial_data2 + partial_data4 = partial_data3 + adc_P * adc_P * adc_P * self.dig["P11"] + + comp_press = partial_out1 + partial_out2 + partial_data4 + + if comp_press < 30000: + comp_press = 30000 + + if comp_press > 125000: + comp_press = 125000 + + return comp_press + def _sample_bme680(self, eventtime): self.write_register('CTRL_HUM', self.os_hum & 0x07) meas = self.os_temp << 5 | self.os_pres << 2 @@ -564,6 +707,12 @@ def read_id(self): params = self.i2c.i2c_read(regs, 1) return bytearray(params['response'])[0] + def read_bmp3_id(self): + # read chip id register + regs = [BMP3_CHIP_ID_REG] + params = self.i2c.i2c_read(regs, 1) + return bytearray(params['response'])[0] + def read_register(self, reg_name, read_len): # read a single register regs = [self.chip_registers[reg_name]] From 0b329c5d28230e8ad19434840bde26f5fd332998 Mon Sep 17 00:00:00 2001 From: Amken USA <166057890+amken3d@users.noreply.github.com> Date: Wed, 24 Apr 2024 22:32:29 -0400 Subject: [PATCH 105/190] rp2040: Add kconfig options for rp2040 uart (#6549) Modified serial.c and Kconfig to dynamically select all possible UART combinations for RP2040 Signed-off-by: Hriday Keni --- src/rp2040/Kconfig | 47 +++++++++++++++++++++-------- src/rp2040/serial.c | 73 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 97 insertions(+), 23 deletions(-) diff --git a/src/rp2040/Kconfig b/src/rp2040/Kconfig index 59891649e6b0..ec8a1af3205d 100644 --- a/src/rp2040/Kconfig +++ b/src/rp2040/Kconfig @@ -95,19 +95,40 @@ config RP2040_STAGE2_CLKDIV ###################################################################### choice - prompt "Communication interface" - config RP2040_USB - bool "USB" - select USBSERIAL - config RP2040_SERIAL_UART0 - bool "Serial (on UART0 GPIO1/GPIO0)" - select SERIAL - config RP2040_CANBUS - bool "CAN bus" - select CANSERIAL - config RP2040_USBCANBUS - bool "USB to CAN bus bridge" - select USBCANBUS + prompt "Communication Interface" + config RP2040_USB + bool "USBSERIAL" + select USBSERIAL + config RP2040_SERIAL_UART0_PINS_0_1 + bool "UART0 on GPIO0/GPIO1" + select SERIAL + config RP2040_SERIAL_UART0_PINS_12_13 + bool "UART0 on GPIO12/GPIO13" if LOW_LEVEL_OPTIONS + select SERIAL + config RP2040_SERIAL_UART0_PINS_16_17 + bool "UART0 on GPIO16/GPIO17" if LOW_LEVEL_OPTIONS + select SERIAL + config RP2040_SERIAL_UART0_PINS_28_29 + bool "UART0 on GPIO28/GPIO29" if LOW_LEVEL_OPTIONS + select SERIAL + config RP2040_SERIAL_UART1_PINS_4_5 + bool "UART1 on GPIO4/GPIO5" if LOW_LEVEL_OPTIONS + select SERIAL + config RP2040_SERIAL_UART1_PINS_8_9 + bool "UART1 on GPIO8/GPIO9" if LOW_LEVEL_OPTIONS + select SERIAL + config RP2040_SERIAL_UART1_PINS_20_21 + bool "UART1 on GPIO20/GPIO21" if LOW_LEVEL_OPTIONS + select SERIAL + config RP2040_SERIAL_UART1_PINS_24_25 + bool "UART1 on GPIO24/GPIO25" if LOW_LEVEL_OPTIONS + select SERIAL + config RP2040_CANBUS + bool "CAN bus" + select CANSERIAL + config RP2040_USBCANBUS + bool "USB to CAN bus bridge" + select USBCANBUS endchoice config RP2040_CANBUS_GPIO_RX diff --git a/src/rp2040/serial.c b/src/rp2040/serial.c index abfdcb8b343b..351a873e55bf 100644 --- a/src/rp2040/serial.c +++ b/src/rp2040/serial.c @@ -4,20 +4,64 @@ // // This file may be distributed under the terms of the GNU GPLv3 license. + #include // uint32_t -#include "autoconf.h" // CONFIG_SERIAL +#include "autoconf.h" // Include configuration header #include "board/armcm_boot.h" // armcm_enable_irq #include "board/irq.h" // irq_save #include "board/serial_irq.h" // serial_rx_data -#include "hardware/structs/resets.h" // RESETS_RESET_UART0_BITS -#include "hardware/structs/uart.h" // UART0_BASE -#include "internal.h" // UART0_IRQn +#include "hardware/structs/resets.h" // RESETS_RESET_UART0/1_BITS +#include "hardware/structs/uart.h" // uart0_hw, uart1_hw +#include "internal.h" // UART0_IRQn, UART1_IRQn #include "sched.h" // DECL_INIT -#define UARTx uart0_hw -#define UARTx_IRQn UART0_IRQ_IRQn -#define GPIO_Rx 1 -#define GPIO_Tx 0 +// Dynamically select UART and IRQ based on configuration + + + #if CONFIG_RP2040_SERIAL_UART0_PINS_0_1 + #define GPIO_Rx 1 + #define GPIO_Tx 0 + #define UARTx uart0_hw + #define UARTx_IRQn UART0_IRQ_IRQn + #elif CONFIG_RP2040_SERIAL_UART0_PINS_12_13 + #define GPIO_Rx 13 + #define GPIO_Tx 12 + #define UARTx uart0_hw + #define UARTx_IRQn UART0_IRQ_IRQn + #elif CONFIG_RP2040_SERIAL_UART0_PINS_16_17 + #define GPIO_Rx 17 + #define GPIO_Tx 16 + #define UARTx uart0_hw + #define UARTx_IRQn UART0_IRQ_IRQn + #elif CONFIG_RP2040_SERIAL_UART0_PINS_28_29 + #define GPIO_Rx 29 + #define GPIO_Tx 28 + #define UARTx uart1_hw + #define UARTx_IRQn UART1_IRQ_IRQn + #define UARTx uart0_hw + #define UARTx_IRQn UART0_IRQ_IRQn + #elif CONFIG_RP2040_SERIAL_UART1_PINS_4_5 + #define GPIO_Rx 5 + #define GPIO_Tx 4 + #define UARTx uart1_hw + #define UARTx_IRQn UART1_IRQ_IRQn + #elif CONFIG_RP2040_SERIAL_UART1_PINS_8_9 + #define GPIO_Rx 9 + #define GPIO_Tx 8 + #define UARTx uart1_hw + #define UARTx_IRQn UART1_IRQ_IRQn + #elif CONFIG_RP2040_SERIAL_UART1_PINS_20_21 + #define GPIO_Rx 20 + #define GPIO_Tx 21 + #define UARTx uart1_hw + #define UARTx_IRQn UART1_IRQ_IRQn + #elif CONFIG_RP2040_SERIAL_UART1_PINS_24_25 + #define GPIO_Rx 24 + #define GPIO_Tx 25 + #define UARTx uart1_hw + #define UARTx_IRQn UART1_IRQ_IRQn + #endif + // Write tx bytes to the serial port static void @@ -67,10 +111,19 @@ serial_enable_tx_irq(void) void serial_init(void) { - enable_pclock(RESETS_RESET_UART0_BITS); + + uint32_t pclk= 0x00; + if (UARTx == uart0_hw){ + enable_pclock(RESETS_RESET_UART0_BITS); + pclk= get_pclock_frequency(RESETS_RESET_UART0_BITS); + } else { + enable_pclock(RESETS_RESET_UART1_BITS); + pclk = get_pclock_frequency(RESETS_RESET_UART1_BITS); + } + // Setup baud - uint32_t pclk = get_pclock_frequency(RESETS_RESET_UART0_BITS); + uint32_t div = DIV_ROUND_CLOSEST(pclk * 4, CONFIG_SERIAL_BAUD); UARTx->ibrd = div >> 6; UARTx->fbrd = div & 0x3f; From b1eec53ff46d78fad6b41647708b452035b9fe81 Mon Sep 17 00:00:00 2001 From: Oleg Gavavka Date: Sat, 27 Apr 2024 18:01:57 +0300 Subject: [PATCH 106/190] pru: BeagleBone Firmware upgrade to Debian 11.7 Bullseye (#6577) * Porting BeagleBone to Kernel 5.10 * Fixing issue with installation for BeagleBone. This fix resolve 2 issue: 1. Conflict with AVR packages. 2. "klipper_pru" script is executed before PRU cores are ready * Adding additional steps to BeagleBone install guide. * Updating BeagleBone documentation, adding different use cases, adding buses configurations SPI, I2C, CAN, UART Signed-off-by: Oleg Gavavka --- docs/Beaglebone.md | 252 +++++- lib/README | 2 +- lib/pru_rpmsg/include/am335x/pru_cfg.h | 249 ------ lib/pru_rpmsg/include/am335x/pru_ctrl.h | 155 ---- lib/pru_rpmsg/include/am335x/pru_ecap.h | 128 --- lib/pru_rpmsg/include/am335x/pru_intc.h | 912 --------------------- lib/pru_rpmsg/include/am335x/pru_uart.h | 285 ------- lib/pru_rpmsg/include/am335x/sys_mailbox.h | 210 ----- lib/pru_rpmsg/include/am335x/sys_pwmss.h | 446 ---------- lib/pru_rpmsg/include/pru_rpmsg.h | 6 +- lib/pru_rpmsg/include/pru_types.h | 73 +- lib/pru_rpmsg/include/pru_virtio_ring.h | 16 +- lib/pru_rpmsg/include/rsc_types.h | 57 +- lib/pru_rpmsg/include/types.h | 18 - lib/pru_rpmsg/pru_virtqueue.c | 15 +- scripts/install-beaglebone.sh | 11 +- src/pru/Makefile | 4 + src/pru/gpio.c | 2 +- src/pru/intc_map_0.h | 62 ++ src/pru/internal.h | 6 + src/pru/main.c | 31 +- src/pru/pru0.c | 135 +-- src/pru/resource_table.h | 91 ++ 23 files changed, 565 insertions(+), 2601 deletions(-) delete mode 100644 lib/pru_rpmsg/include/am335x/pru_cfg.h delete mode 100644 lib/pru_rpmsg/include/am335x/pru_ctrl.h delete mode 100644 lib/pru_rpmsg/include/am335x/pru_ecap.h delete mode 100644 lib/pru_rpmsg/include/am335x/pru_intc.h delete mode 100644 lib/pru_rpmsg/include/am335x/pru_uart.h delete mode 100644 lib/pru_rpmsg/include/am335x/sys_mailbox.h delete mode 100644 lib/pru_rpmsg/include/am335x/sys_pwmss.h delete mode 100644 lib/pru_rpmsg/include/types.h create mode 100644 src/pru/intc_map_0.h create mode 100644 src/pru/resource_table.h diff --git a/docs/Beaglebone.md b/docs/Beaglebone.md index 67fc96572cb4..700da90e032a 100644 --- a/docs/Beaglebone.md +++ b/docs/Beaglebone.md @@ -6,23 +6,64 @@ PRU. ## Building an OS image Start by installing the -[Debian 9.9 2019-08-03 4GB SD IoT](https://beagleboard.org/latest-images) +[Debian 11.7 2023-09-02 4GB microSD IoT](https://beagleboard.org/latest-images) image. One may run the image from either a micro-SD card or from builtin eMMC. If using the eMMC, install it to eMMC now by following the instructions from the above link. Then ssh into the Beaglebone machine (`ssh debian@beaglebone` -- -password is `temppwd`) and install Klipper by running the following +password is `temppwd`). + +Before start installing Klipper you need to free-up additional space. +there are 3 options to do that: +1. remove some BeagleBone "Demo" resources +2. if you did boot from SD-Card, and it's bigger than 4Gb - you can expand +current filesystem to take whole card space +3. do option #1 and #2 together. + +To remove some BeagleBone "Demo" resources execute these commands +``` +sudo apt remove bb-node-red-installer +sudo apt remove bb-code-server +``` + +To expand filesystem to full size of your SD-Card execute this command, reboot is not required. +``` +sudo growpart /dev/mmcblk0 1 +sudo resize2fs /dev/mmcblk0p1 +``` + + +Install Klipper by running the following commands: ``` -git clone https://github.com/Klipper3d/klipper +git clone https://github.com/Klipper3d/klipper.git ./klipper/scripts/install-beaglebone.sh ``` -## Install Octoprint +After installing Klipper you need to decide what kind of deployment do you need, +but take a note that BeagleBone is 3.3v based hardware and in most cases you can't +directly connect pins to 5v or 12v based hardware without conversion boards. + +As Klipper have multimodule architecture on BeagleBone you can achieve many different use cases, +but general ones are following: + +Use case 1: Use BeagleBone only as a host system to run Klipper and additional software +like OctoPrint/Fluidd + Moonraker/... and this configuration will be driving +external micro-controllers via serial/usb/canbus connections. + +Use case 2: Use BeagleBone with extension board (cape) like CRAMPS board. +in this configuration BeagleBone will host Klipper + additional software, and +it will drive extension board with BeagleBone PRU cores (2 additional cores 200Mh, 32Bit). + +Use case 3: It's same as "Use case 1" but additionally you want to drive +BeagleBone GPIOs with high speed by utilizing PRU cores to offload main CPU. + + +## Installing Octoprint -One may then install Octoprint: +One may then install Octoprint or fully skip this section if desired other software: ``` git clone https://github.com/foosel/OctoPrint.git cd OctoPrint/ @@ -51,25 +92,89 @@ Then start the Octoprint service: ``` sudo systemctl start octoprint ``` - -Make sure the OctoPrint web server is accessible - it should be at: +Wait 1-2 minutes and make sure the OctoPrint web server is accessible - it should be at: [http://beaglebone:5000/](http://beaglebone:5000/) -## Building the micro-controller code -To compile the Klipper micro-controller code, start by configuring it -for the "Beaglebone PRU": +## Building the BeagleBone PRU micro-controller code (PRU firmware) +This section is required for "Use case 2" and "Use case 3" mentioned above, +you should skip it for "Use case 1". + +Check that required devices are present + +``` +sudo beagle-version +``` +You should check that output contains successful "remoteproc" drivers loading and presence of PRU cores, +in Kernel 5.10 they should be "remoteproc1" and "remoteproc2" (4a334000.pru, 4a338000.pru) +Also check that many GPIOs are loaded they will look like "Allocated GPIO id=0 name='P8_03'" +Usually everything is fine and no hardware configuration is required. +If something is missing - try to play with "uboot overlays" options or with cape-overlays +Just for reference some output of working BeagleBone Black configuration with CRAMPS board: +``` +model:[TI_AM335x_BeagleBone_Black] +UBOOT: Booted Device-Tree:[am335x-boneblack-uboot-univ.dts] +UBOOT: Loaded Overlay:[BB-ADC-00A0.bb.org-overlays] +UBOOT: Loaded Overlay:[BB-BONE-eMMC1-01-00A0.bb.org-overlays] +kernel:[5.10.168-ti-r71] +/boot/uEnv.txt Settings: +uboot_overlay_options:[enable_uboot_overlays=1] +uboot_overlay_options:[disable_uboot_overlay_video=0] +uboot_overlay_options:[disable_uboot_overlay_audio=1] +uboot_overlay_options:[disable_uboot_overlay_wireless=1] +uboot_overlay_options:[enable_uboot_cape_universal=1] +pkg:[bb-cape-overlays]:[4.14.20210821.0-0~bullseye+20210821] +pkg:[bb-customizations]:[1.20230720.1-0~bullseye+20230720] +pkg:[bb-usb-gadgets]:[1.20230414.0-0~bullseye+20230414] +pkg:[bb-wl18xx-firmware]:[1.20230414.0-0~bullseye+20230414] +............. +............. + +``` + +To compile the Klipper micro-controller code, start by configuring it for the "Beaglebone PRU", +for "BeagleBone Black" additionally disable options "Support GPIO Bit-banging devices" and disable "Support LCD devices" +inside the "Optional features" because they will not fit in 8Kb PRU firmware memory, +then exit and save config: ``` cd ~/klipper/ make menuconfig ``` -To build and install the new micro-controller code, run: +To build and install the new PRU micro-controller code, run: ``` sudo service klipper stop make flash sudo service klipper start ``` +After previous commands was executed your PRU firmware should be ready and started +to check if everything was fine you can execute following command +``` +dmesg +``` +and compare last messages with sample one which indicate that everything started properly: +``` +[ 71.105499] remoteproc remoteproc1: 4a334000.pru is available +[ 71.157155] remoteproc remoteproc2: 4a338000.pru is available +[ 73.256287] remoteproc remoteproc1: powering up 4a334000.pru +[ 73.279246] remoteproc remoteproc1: Booting fw image am335x-pru0-fw, size 97112 +[ 73.285807] remoteproc1#vdev0buffer: registered virtio0 (type 7) +[ 73.285836] remoteproc remoteproc1: remote processor 4a334000.pru is now up +[ 73.286322] remoteproc remoteproc2: powering up 4a338000.pru +[ 73.313717] remoteproc remoteproc2: Booting fw image am335x-pru1-fw, size 188560 +[ 73.313753] remoteproc remoteproc2: header-less resource table +[ 73.329964] remoteproc remoteproc2: header-less resource table +[ 73.348321] remoteproc remoteproc2: remote processor 4a338000.pru is now up +[ 73.443355] virtio_rpmsg_bus virtio0: creating channel rpmsg-pru addr 0x1e +[ 73.443727] virtio_rpmsg_bus virtio0: msg received with no recipient +[ 73.444352] virtio_rpmsg_bus virtio0: rpmsg host is online +[ 73.540993] rpmsg_pru virtio0.rpmsg-pru.-1.30: new rpmsg_pru device: /dev/rpmsg_pru30 +``` +take a note about "/dev/rpmsg_pru30" - it's your future serial device for main mcu configuration +this device is required to be present, if it's absent - your PRU cores did not start properly. + +## Building and installing Linux host micro-controller code +This section is required for "Use case 2" and optional for "Use case 3" mentioned above It is also necessary to compile and install the micro-controller code for a Linux host process. Configure it a second time for a "Linux process": @@ -83,12 +188,24 @@ sudo service klipper stop make flash sudo service klipper start ``` +take a note about "/tmp/klipper_host_mcu" - it will be your future serial device for "mcu host" +if that file don't exist - refer to "scripts/klipper-mcu.service" file, it was installed by +previous commands, and it's responsible for it. + + +Take a note for "Use case 2" about following: when you will define printer configuration you should always +use temperature sensors from "mcu host" because ADCs not present in default "mcu" (PRU cores). +Sample configuration of "sensor_pin" for extruder and heated bed are available in "generic-cramps.cfg" +You can use any other GPIO directly from "mcu host" by referencing them this way "host:gpiochip1/gpio17" +but that should be avoided because it will be creating additional load on main CPU and most probably +you can't use them for stepper control. + ## Remaining configuration -Complete the installation by configuring Klipper and Octoprint +Complete the installation by configuring Klipper following the instructions in -the main [Installation](Installation.md#configuring-klipper) document. +the main [Installation](Installation.md#configuring-octoprint-to-use-klipper) document. ## Printing on the Beaglebone @@ -97,4 +214,111 @@ OctoPrint well. Print stalls have been known to occur on complex prints (the printer may move faster than OctoPrint can send movement commands). If this occurs, consider using the "virtual_sdcard" feature (see [Config Reference](Config_Reference.md#virtual_sdcard) for -details) to print directly from Klipper. +details) to print directly from Klipper +and disable any DEBUG or VERBOSE logging options if you did enable them. + + +## AVR micro-controller code build +This environment have everything to build necessary micro-controller code except AVR, +AVR packages was removed because of conflict with PRU packages. +if you still want to build AVR micro-controller code in this environment you need to remove +PRU packages and install AVR packages by executing following commands + +``` +sudo apt-get remove gcc-pru +sudo apt-get install avrdude gcc-avr binutils-avr avr-libc +``` +if you need to restore PRU packages - then remove ARV packages before that +``` +sudo apt-get remove avrdude gcc-avr binutils-avr avr-libc +sudo apt-get install gcc-pru +``` + + +## Hardware Pin designation +BeagleBone is very flexible in terms of pin designation, same pin can be configured for different function +but always single function for single pin, same function can be present on different pins. +So you can't have multiple functions on single pin or have same function on multiple pins. +Example: +P9_20 - i2c2_sda/can0_tx/spi1_cs0/gpio0_12/uart1_ctsn +P9_19 - i2c2_scl/can0_rx/spi1_cs1/gpio0_13/uart1_rtsn +P9_24 - i2c1_scl/can1_rx/gpio0_15/uart1_tx +P9_26 - i2c1_sda/can1_tx/gpio0_14/uart1_rx + +Pin designation is defined by using special "overlays" which will be loaded during linux boot +they are configured by editing file /boot/uEnv.txt with elevated permissions +``` +sudo editor /boot/uEnv.txt +``` +and defining which functionality to load, for example to enable CAN1 you need to define overlay for it +``` +uboot_overlay_addr4=/lib/firmware/BB-CAN1-00A0.dtbo +``` +This overlay BB-CAN1-00A0.dtbo will reconfigure all required pins for CAN1 and create CAN device in Linux. +Any change in overlays will require system reboot to be applied. +If you need to understand which pins are involved in some overlay - you can analyze source files in +this location: /opt/sources/bb.org-overlays/src/arm/ +or search info in BeagleBone forums. + + +## Enabling hardware SPI +BeagleBone usually have multiple hardware SPI buses, for example BeagleBone Black can have 2 of them, +they can work up to 48Mhz, but usually they are limited to 16Mhz by Kernel Device-tree. +By default, in BeagleBone Black some of SPI1 pins are configured for HDMI-Audio output, +to fully enable 4-wire SPI1 you need to disable HDMI Audio and enable SPI1 +To do that edit file /boot/uEnv.txt with elevated permissions +``` +sudo editor /boot/uEnv.txt +``` +uncomment variable +``` +disable_uboot_overlay_audio=1 +``` + +next uncomment variable and define it this way +``` +uboot_overlay_addr4=/lib/firmware/BB-SPIDEV1-00A0.dtbo +``` +Save changes in /boot/uEnv.txt and reboot the board. +Now you have SPI1 Enabled, to verify its presence execute command +``` +ls /dev/spidev1.* +``` +Take a note that BeagleBone usually is 3.3v based hardware and to use 5V SPI devices +you need to add Level-Shifting chip, for example SN74CBTD3861, SN74LVC1G34 or similar. +If you are using CRAMPS board - it already contains Level-Shifting chip and SPI1 pins +will become available on P503 port, and they can accept 5v hardware, +check CRAMPS board Schematics for pin references. + +## Enabling hardware I2C +BeagleBone usually have multiple hardware I2C buses, for example BeagleBone Black can have 3 of them, +they support speed up-to 400Kbit Fast mode. +By default, in BeagleBone Black there are two of them (i2c-1 and i2c-2) usually both are already configured and +present on P9, third ic2-0 usually reserved for internal use. +If you are using CRAMPS board then i2c-2 is present on P303 port with 3.3v level, +If you want to obtain I2c-1 in CRAMPS board - you can get them on Extruder1.Step, Extruder1.Dir pins, +they also are 3.3v based, check CRAMPS board Schematics for pin references. +Related overlays, for [Hardware Pin designation](#hardware-pin-designation): +I2C1(100Kbit): BB-I2C1-00A0.dtbo +I2C1(400Kbit): BB-I2C1-FAST-00A0.dtbo +I2C2(100Kbit): BB-I2C2-00A0.dtbo +I2C2(400Kbit): BB-I2C2-FAST-00A0.dtbo + +## Enabling hardware UART(Serial)/CAN +BeagleBone have up to 6 hardware UART(Serial) buses (up to 3Mbit) +and up to 2 hardware CAN(1Mbit) buses. +UART1(RX,TX) and CAN1(TX,RX) and I2C2(SDA,SCL) are using same pins - so you need to chose what to use +UART1(CTSN,RTSN) and CAN0(TX,RX) and I2C1(SDA,SCL) are using same pins - so you need to chose what to use +All UART/CAN related pins are 3.3v based, so you will need to use Transceiver chips/boards like SN74LVC2G241DCUR (for UART), +SN65HVD230 (for CAN), TTL-RS485 (for RS-485) or something similar which can convert 3.3v signals to appropriate levels. + +Related overlays, for [Hardware Pin designation](#hardware-pin-designation) +CAN0: BB-CAN0-00A0.dtbo +CAN1: BB-CAN1-00A0.dtbo +UART0: - used for Console +UART1(RX,TX): BB-UART1-00A0.dtbo +UART1(RTS,CTS): BB-UART1-RTSCTS-00A0.dtbo +UART2(RX,TX): BB-UART2-00A0.dtbo +UART3(RX,TX): BB-UART3-00A0.dtbo +UART4(RS-485): BB-UART4-RS485-00A0.dtbo +UART5(RX,TX): BB-UART5-00A0.dtbo diff --git a/lib/README b/lib/README index e981df59f78d..106b2cfc35fa 100644 --- a/lib/README +++ b/lib/README @@ -132,7 +132,7 @@ details. See changes.diff for the modifications. The pru_rpmsg directory contains code from: https://github.com/dinuxbg/pru-gcc-examples -revision 425a42d82006cf0aa24be27b483d2f6a41607489. The code is taken +revision e2bd170d4d61b3e642da65e0f0d487e10872fe22. The code is taken from the repo's hc-sr04-range-sensor directory. It has been modified so that the IEP definitions compile correctly. See pru_rpmsg.patch for the modifications. diff --git a/lib/pru_rpmsg/include/am335x/pru_cfg.h b/lib/pru_rpmsg/include/am335x/pru_cfg.h deleted file mode 100644 index 175867c6fac9..000000000000 --- a/lib/pru_rpmsg/include/am335x/pru_cfg.h +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Texas Instruments Incorporated nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _PRU_CFG_H_ -#define _PRU_CFG_H_ - -/* PRU_CFG register set */ -typedef struct { - - /* PRU_CFG_REVID register bit field */ - union { - volatile uint32_t REVID; - - volatile struct { - unsigned REVID : 32; - } REVID_bit; - }; // 0x0 - - - /* PRU_CFG_SYSCFG register bit field */ - union { - volatile uint32_t SYSCFG; - - volatile struct { - unsigned IDLE_MODE : 2; - unsigned STANDBY_MODE : 2; - unsigned STANDBY_INIT : 1; - unsigned SUB_MWAIT : 1; - unsigned rsvd6 : 26; - } SYSCFG_bit; - }; // 0x4 - - - /* PRU_CFG_GPCFG0 register bit field */ - union { - volatile uint32_t GPCFG0; - - volatile struct { - unsigned PRU0_GPI_MODE : 2; // 1:0 - unsigned PRU0_GPI_CLK_MODE : 1; // 2 - unsigned PRU0_GPI_DIV0 : 5; // 7:3 - unsigned PRU0_GPI_DIV1 : 5; // 12:8 - unsigned PRU0_GPI_SB : 1; // 13 - unsigned PRU0_GPO_MODE : 1; // 14 - unsigned PRU0_GPO_DIV0 : 5; // 19:15 - unsigned PRU0_GPO_DIV1 : 5; // 24:20 - unsigned PRU0_GPO_SH_SEL : 1; // 25 - unsigned rsvd26 : 6; // 31:26 - } GPCFG0_bit; - }; // 0x8 - - - /* PRU_CFG_GPCFG1 register bit field */ - union { - volatile uint32_t GPCFG1; - - volatile struct { - unsigned PRU1_GPI_MODE : 2; // 1:0 - unsigned PRU1_GPI_CLK_MODE : 1; // 2 - unsigned PRU1_GPI_DIV0 : 5; // 7:3 - unsigned PRU1_GPI_DIV1 : 5; // 12:8 - unsigned PRU1_GPI_SB : 1; // 13 - unsigned PRU1_GPO_MODE : 1; // 14 - unsigned PRU1_GPO_DIV0 : 5; // 19:15 - unsigned PRU1_GPO_DIV1 : 5; // 24:20 - unsigned PRU1_GPO_SH_SEL : 1; // 25 - unsigned rsvd26 : 6; // 31:26 - } GPCFG1_bit; - }; // 0xC - - - /* PRU_CFG_CGR register bit field */ - union { - volatile uint32_t CGR; - - volatile struct { - unsigned PRU0_CLK_STOP_REQ : 1; // 0 - unsigned PRU0_CLK_STOP_ACK : 1; // 1 - unsigned PRU0_CLK_EN : 1; // 2 - unsigned PRU1_CLK_STOP_REQ : 1; // 3 - unsigned PRU1_CLK_STOP_ACK : 1; // 4 - unsigned PRU1_CLK_EN : 1; // 5 - unsigned INTC_CLK_STOP_REQ : 1; // 6 - unsigned INTC_CLK_STOP_ACK : 1; // 7 - unsigned INTC_CLK_EN : 1; // 8 - unsigned UART_CLK_STOP_REQ : 1; // 9 - unsigned UART_CLK_STOP_ACK : 1; // 10 - unsigned UART_CLK_EN : 1; // 11 - unsigned ECAP_CLK_STOP_REQ : 1; // 12 - unsigned ECAP_CLK_STOP_ACK : 1; // 13 - unsigned ECAP_CLK_EN : 1; // 14 - unsigned IEP_CLK_STOP_REQ : 1; // 15 - unsigned IEP_CLK_STOP_ACK : 1; // 16 - unsigned IEP_CLK_EN : 1; // 17 - unsigned rsvd18 : 14; // 31:18 - } CGR_bit; - }; // 0x10 - - - /* PRU_CFG_ISRP register bit field */ - union { - volatile uint32_t ISRP; - - volatile struct { - unsigned PRU0_IMEM_PE_RAW : 4; // 3:0 - unsigned PRU0_DMEM_PE_RAW : 4; // 7:4 - unsigned PRU1_IMEM_PE_RAW : 4; // 11:8 - unsigned PRU1_DMEM_PE_RAW : 4; // 15:12 - unsigned RAM_PE_RAW : 4; // 19:16 - unsigned rsvd20 : 12; // 31:20 - } ISRP_bit; - }; // 0x14 - - - /* PRU_CFG_ISP register bit field */ - union { - volatile uint32_t ISP; - - volatile struct { - unsigned PRU0_IMEM_PE : 4; // 3:0 - unsigned PRU0_DMEM_PE : 4; // 7:4 - unsigned PRU1_IMEM_PE : 4; // 11:8 - unsigned PRU1_DMEM_PE : 4; // 15:12 - unsigned RAM_PE : 4; // 19:16 - unsigned rsvd20 : 12; // 31:20 - } ISP_bit; - }; // 0x18 - - /* PRU_CFG_IESP register bit field */ - union { - volatile uint32_t IESP; - - volatile struct { - unsigned PRU0_IMEM_PE_SET : 4; // 3:0 - unsigned PRU0_DMEM_PE_SET : 4; // 7:4 - unsigned PRU1_IMEM_PE_SET : 4; // 11:8 - unsigned PRU1_DMEM_PE_SET : 4; // 15:12 - unsigned RAM_PE_SET : 4; // 19:16 - unsigned rsvd20 : 12; // 31:20 - } IESP_bit; - }; // 0x1C - - - /* PRU_CFG_IECP register bit field */ - union { - volatile uint32_t IECP; - - volatile struct { - unsigned PRU0_IMEM_PE_CLR : 4; // 3:0 - unsigned PRU0_DMEM_PE_CLR : 4; // 7:4 - unsigned PRU1_IMEM_PE_CLR : 4; // 11:8 - unsigned PRU1_DMEM_PE_CLR : 4; // 15:12 - unsigned rsvd16 : 16; // 31:16 - } IECP_bit; - }; // 0x20 - - - uint32_t rsvd24; // 0x24 - - - /* PRU_CFG_PMAO register bit field */ - union { - volatile uint32_t PMAO; - - volatile struct { - unsigned PMAO_PRU0 : 1; // 0 - unsigned PMAO_PRU1 : 1; // 1 - unsigned rsvd2 : 30; // 31:2 - } PMAO_bit; - }; // 0x28 - - - uint32_t rsvd2c[1]; // 0x2C - - - /* PRU_CFG_IEPCLK register bit field */ - union { - volatile uint32_t IEPCLK; - - volatile struct { - unsigned OCP_EN : 1; // 0 - unsigned rsvd1 : 31; // 31:1 - } IEPCLK_bit; - }; // 0x30 - - - /* PRU_CFG_SPP register bit field */ - union { - volatile uint32_t SPP; - - volatile struct { - unsigned PRU1_PAD_HP_EN : 1; // 0 - unsigned XFR_SHIFT_EN : 1; // 1 - unsigned rsvd2 : 30; // 31:2 - } SPP_bit; - }; // 0x34 - - - uint32_t rsvd38[2]; // 0x38 - 0x3C - - - union { - volatile uint32_t PIN_MX; - - volatile struct { - unsigned PIN_MUX_SEL : 8; // 7:0 - unsigned rsvd2 : 24; // 31:8 - } PIN_MX_bit; - }; //0x40 -} pruCfg; - -#ifdef __GNUC__ -static volatile pruCfg *__CT_CFG = (void *)0x00026000; -#define CT_CFG (*__CT_CFG) -#else -volatile __far pruCfg CT_CFG __attribute__((cregister("PRU_CFG", near), peripheral)); -#endif - -#endif /* _PRU_CFG_H_ */ diff --git a/lib/pru_rpmsg/include/am335x/pru_ctrl.h b/lib/pru_rpmsg/include/am335x/pru_ctrl.h deleted file mode 100644 index 3bade9f26dc2..000000000000 --- a/lib/pru_rpmsg/include/am335x/pru_ctrl.h +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Texas Instruments Incorporated nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _PRU_CTRL_H_ -#define _PRU_CTRL_H_ - -/* PRU_CTRL register set */ -typedef struct { - - /* PRU_CTRL_CTRL register bit field */ - union { - volatile uint32_t CTRL; - - volatile struct { - unsigned SOFT_RST_N : 1; - unsigned EN : 1; - unsigned SLEEPING : 1; - unsigned CTR_EN : 1; - unsigned rsvd4 : 4; - unsigned SINGLE_STEP : 1; - unsigned rsvd9 : 6; - unsigned RUNSTATE : 1; - unsigned PCTR_RST_VAL : 16; - } CTRL_bit; - }; // 0x0 - - - /* PRU_CTRL_STS register bit field */ - union { - volatile uint32_t STS; - - volatile struct { - unsigned PCTR : 16; - unsigned rsvd16 : 16; - } STS_bit; - }; // 0x4 - - - /* PRU_CTRL_WAKEUP_EN register bit field */ - union { - volatile uint32_t WAKEUP_EN; - - volatile struct { - unsigned BITWISE_ENS : 32; - } WAKEUP_EN_bit; - }; // 0x8 - - - /* PRU_CTRL_CYCLE register bit field */ - union { - volatile uint32_t CYCLE; - - volatile struct { - unsigned CYCLECOUNT : 32; - } CYCLE_bit; - }; // 0xC - - - /* PRU_CTRL_STALL register bit field */ - union { - volatile uint32_t STALL; - - volatile struct { - unsigned STALLCOUNT : 32; - } STALL_bit; - }; // 0x10 - - - uint32_t rsvd14[3]; // 0x14 - 0x1C - - - /* PRU_CTRL_CTBIR0 register bit field */ - union { - volatile uint32_t CTBIR0; - - volatile struct { - unsigned C24_BLK_IDX : 8; - unsigned rsvd8 : 8; - unsigned C25_BLK_IDX : 8; - unsigned rsvd24 : 8; - } CTBIR0_bit; - }; // 0x20 - - - /* PRU_CTRL_CTBIR1 register bit field */ - union { - volatile uint32_t CTBIR1; - - volatile struct { - unsigned C26_BLK_IDX : 8; - unsigned rsvd8 : 8; - unsigned C27_BLK_IDX : 8; - unsigned rsvd24 : 8; - } CTBIR1_bit; - }; // 0x24 - - - /* PRU_CTRL_CTPPR0 register bit field */ - union { - volatile uint32_t CTPPR0; - - volatile struct { - unsigned C28_BLK_POINTER : 16; - unsigned C29_BLK_POINTER : 16; - } CTPPR0_bit; - }; // 0x28 - - - /* PRU_CTRL_CTPPR1 register bit field */ - union { - volatile uint32_t CTPPR1; - - volatile struct { - unsigned C30_BLK_POINTER : 16; - unsigned C31_BLK_POINTER : 16; - } CTPPR1_bit; - }; // 0x2C - -} pruCtrl; - -/* Definition of control register structures. */ -#define PRU0_CTRL (*((volatile pruCtrl*)0x22000)) -#define PRU1_CTRL (*((volatile pruCtrl*)0x24000)) - -#endif /* _PRU_CTRL_H_ */ diff --git a/lib/pru_rpmsg/include/am335x/pru_ecap.h b/lib/pru_rpmsg/include/am335x/pru_ecap.h deleted file mode 100644 index 8385fc9b686a..000000000000 --- a/lib/pru_rpmsg/include/am335x/pru_ecap.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Texas Instruments Incorporated nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _PRU_ECAP_H_ -#define _PRU_ECAP_H_ - -/* PRU_ECAP register set */ -typedef struct { - - /* PRU_ECAP_TSCTR register bit field */ - union { - volatile uint32_t TSCTR; - - volatile struct { - unsigned TSCTR : 32; //31:0 - } TSCTR_bit; - }; // 0x0 - - /* PRU_ECAP_CTRPHS register bit field */ - union { - volatile uint32_t CTRPHS; - - volatile struct { - unsigned CTRPHS : 32; //31:0 - } CTRPHS_bit; - }; // 0x4 - - /* PRU_ECAP_CAP1 register bit field */ - union { - volatile uint32_t CAP1; - - volatile struct { - unsigned CAP1 : 32; //31:0 - } CAP1_bit; - }; // 0x8 - - /* PRU_ECAP_CAP2 register bit field */ - union { - volatile uint32_t CAP2; - - volatile struct { - unsigned CAP2 : 32; //31:0 - } CAP2_bit; - }; // 0xC - - /* PRU_ECAP_CAP3 register bit field */ - union { - volatile uint32_t CAP3; - - volatile struct { - unsigned CAP3 : 32; //31:0 - } CAP3_bit; - }; // 0x10 - - /* PRU_ECAP_CAP4 register bit field */ - union { - volatile uint32_t CAP4; - - volatile struct { - unsigned CAP4 : 32; //31:0 - } CAP4_bit; - }; // 0x14 - - uint32_t rsvd118[4]; // 0x118 - 0x124 - - /* PRU_ECAP_ECCTL1 register bit field */ - volatile uint16_t ECCTL1; // 0x28 - - /* PRU_ECAP_ECCTL2 register bit field */ - volatile uint16_t ECCTL2; // 0x2A - - /* PRU_ECAP_ECEINT register bit field */ - volatile uint16_t ECEINT; // 0x2C - - /* PRU_ECAP_ECFLG register bit field */ - volatile uint16_t ECFLG; // 0x2E - - /* PRU_ECAP_ECCLR register bit field */ - volatile uint16_t ECCLR; // 0x30 - - /* PRU_ECAP_ECFRC register bit field */ - volatile uint16_t ECFRC; // 0x32 - - uint32_t rsvd34[10]; // 0x34 - 0x58 - - /* PRU_ECAP_REVID register bit field */ - union { - volatile uint32_t REVID; - - volatile struct { - unsigned REV : 32; //31:0 - } REVID_bit; - }; // 0x5C -} pruEcap; - -volatile __far pruEcap CT_ECAP __attribute__((cregister("PRU_ECAP", near), peripheral)); - -#endif /* _PRU_ECAP_H_ */ diff --git a/lib/pru_rpmsg/include/am335x/pru_intc.h b/lib/pru_rpmsg/include/am335x/pru_intc.h deleted file mode 100644 index 1291940da500..000000000000 --- a/lib/pru_rpmsg/include/am335x/pru_intc.h +++ /dev/null @@ -1,912 +0,0 @@ -/* - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Texas Instruments Incorporated nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _PRU_INTC_H_ -#define _PRU_INTC_H_ - -/* PRU INTC register set */ -typedef struct { - - /* PRU_INTC_REVID register bit field */ - union { - volatile uint32_t REVID; - - volatile struct { - unsigned REV_MINOR : 6; // 5:0 - unsigned REV_CUSTOM : 2; // 7:6 - unsigned REV_MAJOR : 3; // 10:8 - unsigned REV_RTL : 5; // 15:11 - unsigned REV_MODULE : 12; // 27:16 - unsigned rsvd28 : 2; // 29:28 - unsigned REV_SCHEME : 2; // 31:30 - } REVID_bit; - }; // 0x0 - - - /* PRU_INTC_CR register bit field */ - union { - volatile uint32_t CR; - - volatile struct { - unsigned rsvd0 : 2; // 1:0 - unsigned NEST_MODE : 2; // 3:2 - unsigned rsvd4 : 28; // 31:4 - } CR_bit; - }; // 0x4 - - - uint32_t rsvd8[2]; // 0x8 - 0xC - - - /* PRU_INTC_GER register bit field */ - union { - volatile uint32_t GER; - - volatile struct { - unsigned EN_HINT_ANY : 1; // 0 - unsigned rsvd1 : 31; // 31:1 - } GER_bit; - }; // 0x10 - - - uint32_t rsvd14[2]; // 0x14 - 0x18 - - - /* PRU_INTC_GNLR register bit field */ - union { - volatile uint32_t GNLR; - - volatile struct { - unsigned GLB_NEST_LEVEL : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } GNLR_bit; - }; // 0x1C - - - /* PRU_INTC_SISR register bit field */ - union { - volatile uint32_t SISR; - - volatile struct { - unsigned STS_SET_IDX : 10; // 9:0 - unsigned rsvd10 : 22; // 31:10 - } SISR_bit; - }; // 0x20 - - - /* PRU_INTC_SICR register bit field */ - union { - volatile uint32_t SICR; - - volatile struct { - unsigned STS_CLR_IDX : 10; // 9:0 - unsigned rsvd10 : 22; // 31:10 - } SICR_bit; - }; // 0x24 - - - /* PRU_INTC_EISR register bit field */ - union { - volatile uint32_t EISR; - - volatile struct { - unsigned EN_SET_IDX : 10; // 9:0 - unsigned rsvd10 : 22; // 31:10 - } EISR_bit; - }; // 0x28 - - - /* PRU_INTC_EICR register bit field */ - union { - volatile uint32_t EICR; - - volatile struct { - unsigned EN_CLR_IDX : 10; // 9:0 - unsigned rsvd10 : 22; // 31:10 - } EICR_bit; - }; // 0x2C - - - uint32_t rsvd30; // 0x30 - - - /* PRU_INTC_HIEISR register bit field */ - union { - volatile uint32_t HIEISR; - - volatile struct { - unsigned HINT_EN_SET_IDX : 4; // 3:0 - unsigned rsvd4 : 28; // 31:4 - } HIEISR_bit; - }; // 0x34 - - - /* PRU_INTC_HIDISR register bit field */ - union { - volatile uint32_t HIDISR; - - volatile struct { - unsigned HINT_EN_CLR_IDX : 4; // 3:0 - unsigned rsvd4 : 28; // 31:4 - } HIDISR_bit; - }; // 0x38 - - - uint32_t rsvd3C[17]; // 0x3C - 0x7C - - - /* PRU_INTC_GPIR register bit field */ - union { - volatile uint32_t GPIR; - - volatile struct { - unsigned GLB_PRI_INTR : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned GLB_NONE : 1; // 31 - } GPIR_bit; - }; // 0x80 - - - uint32_t rsvd84[95]; // 0x84 - 0x1FC - - - /* PRU_INTC_SRSR0 register bit field */ - union { - volatile uint32_t SRSR0; - - volatile struct { - unsigned RAW_STS_31_0 : 32; // 31:0 - } SRSR0_bit; - }; // 0x200 - - - /* PRU_INTC_SRSR1 register bit field */ - union { - volatile uint32_t SRSR1; - - volatile struct { - unsigned RAW_STS_63_32 : 32; // 31:0 - } SRSR1_bit; - }; // 0x204 - - - uint32_t rsvd208[30]; // 0x208 - 0x27C - - - /* PRU_INTC_SECR0 register bit field */ - union { - volatile uint32_t SECR0; - - volatile struct { - unsigned ENA_STS_31_0 : 32; // 31:0 - } SECR0_bit; - }; // 0x280 - - - /* PRU_INTC_SECR1 register bit field */ - union { - volatile uint32_t SECR1; - - volatile struct { - unsigned ENA_STS_63_32 : 32; // 31:0 - } SECR1_bit; - }; // 0x284 - - - uint32_t rsvd288[30]; // 0x288 - 0x2FC - - - /* PRU_INTC_ESR0 register bit field */ - union { - volatile uint32_t ESR0; - - volatile struct { - unsigned EN_SET_31_0 : 32; // 31:0 - } ESR0_bit; - }; // 0x300 - - - /* PRU_INTC_ESR1 register bit field */ - union { - volatile uint32_t ESR1; - - volatile struct { - unsigned EN_SET_63_32 : 32; // 31:0 - } ESR1_bit; - }; // 0x304 - - - uint32_t rsvd308[30]; // 0x308 - 0x37C - - - /* PRU_INTC_ECR0 register bit field */ - union { - volatile uint32_t ECR0; - - volatile struct { - unsigned EN_CLR_31_0 : 32; // 31:0 - } ECR0_bit; - }; // 0x380 - - - /* PRU_INTC_ECR1 register bit field */ - union { - volatile uint32_t ECR1; - - volatile struct { - unsigned EN_CLR_63_32 : 32; // 31:0 - } ECR1_bit; - }; // 0x384 - - - uint32_t rsvd388[30]; // 0x388 - 0x3FC - - - /* PRU_INTC_CMR0 register bit field */ - union { - volatile uint32_t CMR0; - - volatile struct { - unsigned CH_MAP_0 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_1 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_2 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_3 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR0_bit; - }; // 0x400 - - - /* PRU_INTC_CMR1 register bit field */ - union { - volatile uint32_t CMR1; - - volatile struct { - unsigned CH_MAP_4 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_5 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_6 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_7 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR1_bit; - }; // 0x404 - - - /* PRU_INTC_CMR2 register bit field */ - union { - volatile uint32_t CMR2; - - volatile struct { - unsigned CH_MAP_8 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_9 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_10 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_11 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR2_bit; - }; // 0x408 - - - /* PRU_INTC_CMR3 register bit field */ - union { - volatile uint32_t CMR3; - - volatile struct { - unsigned CH_MAP_12 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_13 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_14 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_15 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR3_bit; - }; // 0x40C - - - /* PRU_INTC_CMR4 register bit field */ - union { - volatile uint32_t CMR4; - - volatile struct { - unsigned CH_MAP_16 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_17 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_18 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_19 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR4_bit; - }; // 0x410 - - - /* PRU_INTC_CMR5 register bit field */ - union { - volatile uint32_t CMR5; - - volatile struct { - unsigned CH_MAP_20 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_21 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_22 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_23 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR5_bit; - }; // 0x414 - - - /* PRU_INTC_CMR6 register bit field */ - union { - volatile uint32_t CMR6; - - volatile struct { - unsigned CH_MAP_24 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_25 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_26 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_27 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR6_bit; - }; // 0x418 - - - /* PRU_INTC_CMR7 register bit field */ - union { - volatile uint32_t CMR7; - - volatile struct { - unsigned CH_MAP_28 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_29 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_30 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_31 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR7_bit; - }; // 0x41C - - - /* PRU_INTC_CMR8 register bit field */ - union { - volatile uint32_t CMR8; - - volatile struct { - unsigned CH_MAP_32 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_33 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_34 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_35 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR8_bit; - }; // 0x420 - - - /* PRU_INTC_CMR9 register bit field */ - union { - volatile uint32_t CMR9; - - volatile struct { - unsigned CH_MAP_36 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_37 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_38 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_39 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR9_bit; - }; // 0x424 - - - /* PRU_INTC_CMR10 register bit field */ - union { - volatile uint32_t CMR10; - - volatile struct { - unsigned CH_MAP_40 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_41 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_42 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_43 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR10_bit; - }; // 0x428 - - - /* PRU_INTC_CMR11 register bit field */ - union { - volatile uint32_t CMR11; - - volatile struct { - unsigned CH_MAP_44 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_45 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_46 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_47 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR11_bit; - }; // 0x42C - - - /* PRU_INTC_CMR12 register bit field */ - union { - volatile uint32_t CMR12; - - volatile struct { - unsigned CH_MAP_48 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_49 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_50 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_51 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR12_bit; - }; // 0x430 - - - /* PRU_INTC_CMR13 register bit field */ - union { - volatile uint32_t CMR13; - - volatile struct { - unsigned CH_MAP_52 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_53 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_54 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_55 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR13_bit; - }; // 0x434 - - - /* PRU_INTC_CMR14 register bit field */ - union { - volatile uint32_t CMR14; - - volatile struct { - unsigned CH_MAP_56 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_57 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_58 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_59 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR14_bit; - }; // 0x438 - - - /* PRU_INTC_CMR15 register bit field */ - union { - volatile uint32_t CMR15; - - volatile struct { - unsigned CH_MAP_60 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned CH_MAP_61 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned CH_MAP_62 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned CH_MAP_63 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } CMR15_bit; - }; // 0x43C - - - uint32_t rsvd440[240]; // 0x440 - 0x7FC - - - /* PRU_INTC_HMR0 register bit field */ - union { - volatile uint32_t HMR0; - - volatile struct { - unsigned HINT_MAP_0 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned HINT_MAP_1 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned HINT_MAP_2 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned HINT_MAP_3 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } HMR0_bit; - }; // 0x800 - - - /* PRU_INTC_HMR1 register bit field */ - union { - volatile uint32_t HMR1; - - volatile struct { - unsigned HINT_MAP_4 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned HINT_MAP_5 : 4; // 11:8 - unsigned rsvd12 : 4; // 15:12 - unsigned HINT_MAP_6 : 4; // 19:16 - unsigned rsvd20 : 4; // 23:20 - unsigned HINT_MAP_7 : 4; // 27:24 - unsigned rsvd28 : 4; // 31:28 - } HMR1_bit; - }; // 0x804 - - - /* PRU_INTC_HMR2 register bit field */ - union { - volatile uint32_t HMR2; - - volatile struct { - unsigned HINT_MAP_8 : 4; // 3:0 - unsigned rsvd4 : 4; // 7:4 - unsigned HINT_MAP_9 : 4; // 11:8 - unsigned rsvd12 : 20; // 31:12 - } HMR2_bit; - }; // 0x808 - - - uint32_t rsvd80C[61]; // 0x80C - 0x8FC - - - /* PRU_INTC_HIPIR0 register bit field */ - union { - volatile uint32_t HIPIR0; - - volatile struct { - unsigned PRI_HINT_0 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_0 : 1; // 31 - } HIPIR0_bit; - }; // 0x900 - - - /* PRU_INTC_HIPIR1 register bit field */ - union { - volatile uint32_t HIPIR1; - - volatile struct { - unsigned PRI_HINT_1 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_1 : 1; // 31 - } HIPIR1_bit; - }; // 0x904 - - - /* PRU_INTC_HIPIR2 register bit field */ - union { - volatile uint32_t HIPIR2; - - volatile struct { - unsigned PRI_HINT_2 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_2 : 1; // 31 - } HIPIR2_bit; - }; // 0x908 - - - /* PRU_INTC_HIPIR3 register bit field */ - union { - volatile uint32_t HIPIR3; - - volatile struct { - unsigned PRI_HINT_3 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_3 : 1; // 31 - } HIPIR3_bit; - }; // 0x90C - - - /* PRU_INTC_HIPIR4 register bit field */ - union { - volatile uint32_t HIPIR4; - - volatile struct { - unsigned PRI_HINT_4 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_4 : 1; // 31 - } HIPIR4_bit; - }; // 0x910 - - - /* PRU_INTC_HIPIR5 register bit field */ - union { - volatile uint32_t HIPIR5; - - volatile struct { - unsigned PRI_HINT_5 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_5 : 1; // 31 - } HIPIR5_bit; - }; // 0x914 - - - /* PRU_INTC_HIPIR6 register bit field */ - union { - volatile uint32_t HIPIR6; - - volatile struct { - unsigned PRI_HINT_6 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_6 : 1; // 31 - } HIPIR6_bit; - }; // 0x918 - - - /* PRU_INTC_HIPIR7 register bit field */ - union { - volatile uint32_t HIPIR7; - - volatile struct { - unsigned PRI_HINT_7 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_7 : 1; // 31 - } HIPIR7_bit; - }; // 0x91C - - - /* PRU_INTC_HIPIR8 register bit field */ - union { - volatile uint32_t HIPIR8; - - volatile struct { - unsigned PRI_HINT_8 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_8 : 1; // 31 - } HIPIR8_bit; - }; // 0x920 - - - /* PRU_INTC_HIPIR9 register bit field */ - union { - volatile uint32_t HIPIR9; - - volatile struct { - unsigned PRI_HINT_9 : 10; // 9:0 - unsigned rsvd10 : 21; // 30:10 - unsigned NONE_HINT_9 : 1; // 31 - } HIPIR9_bit; - }; // 0x924 - - - uint32_t rsvd928[246]; // 0x928 - 0xCFC - - - /* PRU_INTC_SIPR0 register bit field */ - union { - volatile uint32_t SIPR0; - - volatile struct { - unsigned POLARITY_31_0 : 32; // 31:0 - } SIPR0_bit; - }; // 0xD00 - - - /* PRU_INTC_SIPR1 register bit field */ - union { - volatile uint32_t SIPR1; - - volatile struct { - unsigned POLARITY_63_32 : 32; // 31:0 - } SIPR1_bit; - }; // 0xD04 - - - uint32_t rsvdD08[30]; // 0xD08 - 0xD7C - - - /* PRU_INTC_SITR0 register bit field */ - union { - volatile uint32_t SITR0; - - volatile struct { - unsigned TYPE_31_0 : 32; // 31:0 - } SITR0_bit; - }; // 0xD80 - - - /* PRU_INTC_SITR1 register bit field */ - union { - volatile uint32_t SITR1; - - volatile struct { - unsigned TYPE_63_32 : 32; // 31:0 - } SITR1_bit; - }; // 0xD84 - - - uint32_t rsvdD84[222]; // 0xD88 - 0x10FC - - - /* PRU_INTC_HINLR0 register bit field */ - union { - volatile uint32_t HINLR0; - - volatile struct { - unsigned NEST_HINT_0 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR0_bit; - }; // 0x1100 - - - /* PRU_INTC_HINLR1 register bit field */ - union { - volatile uint32_t HINLR1; - - volatile struct { - unsigned NEST_HINT_1 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR1_bit; - }; // 0x1104 - - - /* PRU_INTC_HINLR2 register bit field */ - union { - volatile uint32_t HINLR2; - - volatile struct { - unsigned NEST_HINT_2 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR2_bit; - }; // 0x1108 - - - /* PRU_INTC_HINLR3 register bit field */ - union { - volatile uint32_t HINLR3; - - volatile struct { - unsigned NEST_HINT_3 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR3_bit; - }; // 0x110C - - - /* PRU_INTC_HINLR4 register bit field */ - union { - volatile uint32_t HINLR4; - - volatile struct { - unsigned NEST_HINT_4 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR4_bit; - }; // 0x1110 - - - /* PRU_INTC_HINLR5 register bit field */ - union { - volatile uint32_t HINLR5; - - volatile struct { - unsigned NEST_HINT_5 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR5_bit; - }; // 0x1114 - - - /* PRU_INTC_HINLR6 register bit field */ - union { - volatile uint32_t HINLR6; - - volatile struct { - unsigned NEST_HINT_6 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR6_bit; - }; // 0x1118 - - - /* PRU_INTC_HINLR7 register bit field */ - union { - volatile uint32_t HINLR7; - - volatile struct { - unsigned NEST_HINT_7 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR7_bit; - }; // 0x111C - - - /* PRU_INTC_HINLR8 register bit field */ - union { - volatile uint32_t HINLR8; - - volatile struct { - unsigned NEST_HINT_8 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR8_bit; - }; // 0x1120 - - - /* PRU_INTC_HINLR9 register bit field */ - union { - volatile uint32_t HINLR9; - - volatile struct { - unsigned NEST_HINT_9 : 9; // 8:0 - unsigned rsvd9 : 22; // 30:9 - unsigned AUTO_OVERRIDE : 1; // 31 - } HINLR9_bit; - }; // 0x1124 - - - uint32_t rsvd1128[246]; // 0x1128 - 0x14FC - - - /* PRU_INTC_HIER register bit field */ - union { - volatile uint32_t HIER; - - volatile struct { - unsigned EN_HINT : 10; // 9:0 - unsigned rsvd9 : 22; // 31:10 - } HIER_bit; - }; // 0x1500 - -} pruIntc; - -#ifdef __GNUC__ -static volatile pruIntc *__CT_INTC = (void *)0x00020000; -#define CT_INTC (*__CT_INTC) -#else -volatile __far pruIntc CT_INTC __attribute__((cregister("PRU_INTC", far), peripheral)); -#endif - -#endif /* _PRU_INTC_H_ */ diff --git a/lib/pru_rpmsg/include/am335x/pru_uart.h b/lib/pru_rpmsg/include/am335x/pru_uart.h deleted file mode 100644 index 999f81ab7d86..000000000000 --- a/lib/pru_rpmsg/include/am335x/pru_uart.h +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Texas Instruments Incorporated nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _PRU_UART_H_ -#define _PRU_UART_H_ - -/* UART Register set */ -typedef struct { - - /* - * RBR and THR register pair - * This is a unique register pair in that RBR and THR - * share the same address. RBR is read-only while THR is - * write-only. - * - * Additionally, RBR and THR share an address with DLL. To - * read/write RBR/THR write 0 to the DLAB bit in the LCR - * register. To modify DLL write a 1. - * - * DLL also has a dedicated - * address which does not require toggling the DLAB bit. - */ - union { - /* PRU_UART_RBR register bit field */ - union { - volatile uint32_t RBR; - - volatile struct { - unsigned DATA : 8; // 7:0 - unsigned rsvd8 : 24; // 31:8 - } RBR_bit; - }; - - /* PRU_UART_THR register bit field */ - union { - volatile uint32_t THR; - - volatile struct { - unsigned DATA : 8; // 7:0 - unsigned rsvd8 : 24; // 31:8 - } THR_bit; - }; - }; // 0x0 - - - /* PRU_UART_IER register bit field */ - /* - * IER shares an address with DLH. To modify IER write 0 - * to the DLAB bit in the LCR register. To modify DLH write a 1. - * - * DLH also has a dedicated address which does not require - * toggling the DLAB bit. - */ - union { - volatile uint32_t IER; - - volatile struct { - unsigned ERBI : 1; // 0 - unsigned ETBEI : 1; // 1 - unsigned ELSI : 1; // 2 - unsigned EDSSI : 1; // 3 - unsigned rsvd4 : 28; // 31:4 - } IER_bit; - }; // 0x4 - - - /* - * IIR and FCR register pair - * This is a unique register pair in that IIR and FCR - * share the same address. IIR is read-only while FCR is - * write-only. - */ - union { - /* PRU_UART_IIR register bit field */ - union { - volatile uint32_t IIR; - - volatile struct { - unsigned IPEND : 1; // 0 - unsigned INTID : 3; // 3:1 - unsigned rsvd4 : 2; // 5:4 - unsigned FIFOEN : 2; // 7:6 - unsigned rsvd8 : 24; // 31:8 - } IIR_bit; - }; - - /* PRU_UART_FCR register bit field */ - union { - volatile uint32_t FCR; - - volatile struct { - unsigned FIFOEN : 1; // 0 - unsigned RXCLR : 1; // 1 - unsigned TXCLR : 1; // 2 - unsigned DMAMODE1 : 1; // 3 - unsigned rsvd4 : 2; // 5:4 - unsigned RXFIFTL : 2; // 7:6 - unsigned rsvd8 : 24; // 31:8 - } FCR_bit; - }; - }; // 0x8 - - - /* PRU_UART_LCR register bit field */ - union { - volatile uint32_t LCR; - - volatile struct { - unsigned WLS : 2; // 1:0 - unsigned STB : 1; // 2 - unsigned PEN : 1; // 3 - unsigned EPS : 1; // 4 - unsigned SP : 1; // 5 - unsigned BC : 1; // 6 - unsigned DLAB : 1; // 7 - unsigned rsvd8 : 24; // 31:8 - } LCR_bit; - }; // 0xC - - - /* PRU_UART_MCR register bit field */ - union { - volatile uint32_t MCR; - - volatile struct { - unsigned rsvd0 : 1; // 0 - unsigned RTS : 1; // 1 - unsigned OUT1 : 1; // 2 - unsigned OUT2 : 1; // 3 - unsigned LOOP : 1; // 4 - unsigned AFE : 1; // 5 - unsigned rsvd8 : 26; // 31:6 - } MCR_bit; - }; // 0x10 - - - /* PRU_UART_LSR register bit field */ - union { - volatile uint32_t LSR; - - volatile struct { - unsigned DR : 1; // 0 - unsigned OE : 1; // 1 - unsigned PE : 1; // 2 - unsigned FE : 1; // 3 - unsigned BI : 1; // 4 - unsigned THRE : 1; // 5 - unsigned TEMT : 1; // 6 - unsigned RXFIFOE : 1; // 7 - unsigned rsvd8 : 24; // 31:8 - } LSR_bit; - }; // 0x14 - - - /* PRU_UART_MSR register bit field */ - union { - volatile uint32_t MSR; - - volatile struct { - unsigned DCTS : 1; // 0 - unsigned DDSR : 1; // 1 - unsigned TERI : 1; // 2 - unsigned DCD : 1; // 3 - unsigned CTS : 1; // 4 - unsigned DSR : 1; // 5 - unsigned RI : 1; // 6 - unsigned CD : 1; // 7 - unsigned rsvd8 : 24; // 31:8 - } MSR_bit; - }; // 0x18 - - - /* PRU_UART_SCR register bit field */ - union { - volatile uint32_t SCR; - - volatile struct { - unsigned SCR : 8; // 7:0 - unsigned rsvd8 : 24; // 31:8 - } SCR_bit; - }; // 0x1C - - - /* PRU_UART_DLL register bit field */ - union { - volatile uint32_t DLL; - - volatile struct { - unsigned DLL : 8; // 7:0 - unsigned rsvd8 : 24; // 31:8 - } DLL_bit; - }; // 0x20 - - - /* PRU_UART_DLH register bit field */ - union { - volatile uint32_t DLH; - - volatile struct { - unsigned DLH : 8; // 7:0 - unsigned rsvd8 : 24; // 31:8 - } DLH_bit; - }; // 0x24 - - - /* PRU_UART_REVID1 register bit field */ - union { - volatile uint32_t REVID1; - - volatile struct { - unsigned REVID1 : 32; // 31:0 - } REVID1_bit; - }; // 0x28 - - - /* PRU_UART_REVID2 register bit field */ - union { - volatile uint32_t REVID2; - - volatile struct { - unsigned REVID2 : 8; // 7:0 - unsigned rsvd8 : 24; // 31:8 - } REVID2_bit; - }; // 0x2C - - - /* PRU_UART_PWREMU_MGMT register bit field */ - union { - volatile uint32_t PWREMU_MGMT; - - volatile struct { - unsigned FREE : 1; // 0 - unsigned rsvd1 : 12; // 12:1 - unsigned URRST : 1; // 13 - unsigned UTRST : 1; // 14 - unsigned rsvd15 : 17; // 31:15 - } PWREMU_MGMT_bit; - }; // 0x30 - - - /* PRU_UART_MDR register bit field */ - union { - volatile uint32_t MDR; - - volatile struct { - unsigned OSM_SEL : 1; // 0 - unsigned rsvd1 : 31; // 31:1 - } MDR_bit; - }; // 0x34 - -} pruUart; - -volatile __far pruUart CT_UART __attribute__((cregister("PRU_UART", near), peripheral)); - -#endif /* _PRU_UART_H_ */ diff --git a/lib/pru_rpmsg/include/am335x/sys_mailbox.h b/lib/pru_rpmsg/include/am335x/sys_mailbox.h deleted file mode 100644 index f2750f75e11b..000000000000 --- a/lib/pru_rpmsg/include/am335x/sys_mailbox.h +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Texas Instruments Incorporated nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _SYS_MAILBOX_H_ -#define _SYS_MAILBOX_H_ - -/* SYS_MAILBOX register set */ -typedef struct { - - /* SYS_MAILBOX_REVISION register bit field */ - union { - volatile uint32_t REVISION; - - volatile struct { - unsigned MINOR : 6; //5:0 - unsigned CUSTOM : 2; //7:6 - unsigned MAJOR : 3; //10:8 - unsigned RTL : 5; //15:11 - unsigned FUNC : 12; //27:16 - unsigned rsvd28 : 2; //29:28 - unsigned SCHEME : 2; //31:30 - } REVISION_bit; - }; // 0x0 - - uint32_t rsvd4[3]; // 0x4 - 0xC - - /* SYS_MAILBOX_SYSCONFIG register bit field */ - union { - volatile uint32_t SYSCONFIG; - - volatile struct { - unsigned SOFTRESET : 1; //0 - unsigned rsvd : 1; //1 - unsigned SLIDLEMODE : 2; //3:2 - unsigned rsvd1 : 28; //31:4 - } SYSCONFIG_bit; - }; // 0x10 - - uint32_t rsvd14[11]; // 0x14 - 0x3C - - /* SYS_MAILBOX_MESSAGE register bit fields */ - union { - volatile uint32_t MESSAGE[8]; - - volatile struct { - unsigned MESSAGE : 32; //31:0 - } MESSAGE_bit[8]; - }; // 0x40-0x5C - - uint32_t rsvd60[8]; // 0x60 - 0x7C - - /* SYS_MAILBOX_FIFOSTATUS register bit fields */ - union { - volatile uint32_t FIFOSTATUS[8]; - - volatile struct { - unsigned FIFOFULL : 1; //0 - unsigned rsvd : 31; //31:1 - } FIFOSTATUS_bit[8]; - }; // 0x80-0x9C - - uint32_t rsvdA0[8]; // 0xA0 - 0xBC - - /* SYS_MAILBOX_MSGSTATUS register bit fields */ - union { - volatile uint32_t MSGSTATUS[8]; - - volatile struct { - unsigned NBOFMSG : 3; //2:0 - unsigned rsvd : 29; //31:3 - } MSGSTATUS_bit[8]; - }; // 0xC0-DC - - uint32_t rsvdE0[8]; // 0xE0 - 0xFC - - volatile struct { - union { - volatile uint32_t STATUS_RAW; - - volatile struct { - unsigned NEWMSGSTATUSMB0 : 1; //0 - unsigned NOTFULLSTATUSMB0 : 1; //1 - unsigned NEWMSGSTATUSMB1 : 1; //2 - unsigned NOTFULLSTATUSMB1 : 1; //3 - unsigned NEWMSGSTATUSMB2 : 1; //4 - unsigned NOTFULLSTATUSMB2 : 1; //5 - unsigned NEWMSGSTATUSMB3 : 1; //6 - unsigned NOTFULLSTATUSMB3 : 1; //7 - unsigned NEWMSGSTATUSMB4 : 1; //8 - unsigned NOTFULLSTATUSMB4 : 1; //9 - unsigned NEWMSGSTATUSMB5 : 1; //10 - unsigned NOTFULLSTATUSMB5 : 1; //11 - unsigned NEWMSGSTATUSMB6 : 1; //12 - unsigned NOTFULLSTATUSMB6 : 1; //13 - unsigned NEWMSGSTATUSMB7 : 1; //14 - unsigned NOTFULLSTATUSMB7 : 1; //15 - unsigned rsvd : 16; //31:16 - } STATUS_RAW_bit; - }; - union { - volatile uint32_t STATUS_CLR; - - volatile struct { - unsigned NEWMSGSTATUSMB0 : 1; //0 - unsigned NOTFULLSTATUSMB0 : 1; //1 - unsigned NEWMSGSTATUSMB1 : 1; //2 - unsigned NOTFULLSTATUSMB1 : 1; //3 - unsigned NEWMSGSTATUSMB2 : 1; //4 - unsigned NOTFULLSTATUSMB2 : 1; //5 - unsigned NEWMSGSTATUSMB3 : 1; //6 - unsigned NOTFULLSTATUSMB3 : 1; //7 - unsigned NEWMSGSTATUSMB4 : 1; //8 - unsigned NOTFULLSTATUSMB4 : 1; //9 - unsigned NEWMSGSTATUSMB5 : 1; //10 - unsigned NOTFULLSTATUSMB5 : 1; //11 - unsigned NEWMSGSTATUSMB6 : 1; //12 - unsigned NOTFULLSTATUSMB6 : 1; //13 - unsigned NEWMSGSTATUSMB7 : 1; //14 - unsigned NOTFULLSTATUSMB7 : 1; //15 - unsigned rsvd : 16; //31:16 - } STATUS_CLR_bit; - }; - union { - volatile uint32_t ENABLE_SET; - - volatile struct { - unsigned NEWMSGSTATUSMB0 : 1; //0 - unsigned NOTFULLSTATUSMB0 : 1; //1 - unsigned NEWMSGSTATUSMB1 : 1; //2 - unsigned NOTFULLSTATUSMB1 : 1; //3 - unsigned NEWMSGSTATUSMB2 : 1; //4 - unsigned NOTFULLSTATUSMB2 : 1; //5 - unsigned NEWMSGSTATUSMB3 : 1; //6 - unsigned NOTFULLSTATUSMB3 : 1; //7 - unsigned NEWMSGSTATUSMB4 : 1; //8 - unsigned NOTFULLSTATUSMB4 : 1; //9 - unsigned NEWMSGSTATUSMB5 : 1; //10 - unsigned NOTFULLSTATUSMB5 : 1; //11 - unsigned NEWMSGSTATUSMB6 : 1; //12 - unsigned NOTFULLSTATUSMB6 : 1; //13 - unsigned NEWMSGSTATUSMB7 : 1; //14 - unsigned NOTFULLSTATUSMB7 : 1; //15 - unsigned rsvd : 16; //31:16 - } ENABLE_SET_bit; - }; - union { - volatile uint32_t ENABLE_CLR; - - volatile struct { - unsigned NEWMSGSTATUSMB0 : 1; //0 - unsigned NOTFULLSTATUSMB0 : 1; //1 - unsigned NEWMSGSTATUSMB1 : 1; //2 - unsigned NOTFULLSTATUSMB1 : 1; //3 - unsigned NEWMSGSTATUSMB2 : 1; //4 - unsigned NOTFULLSTATUSMB2 : 1; //5 - unsigned NEWMSGSTATUSMB3 : 1; //6 - unsigned NOTFULLSTATUSMB3 : 1; //7 - unsigned NEWMSGSTATUSMB4 : 1; //8 - unsigned NOTFULLSTATUSMB4 : 1; //9 - unsigned NEWMSGSTATUSMB5 : 1; //10 - unsigned NOTFULLSTATUSMB5 : 1; //11 - unsigned NEWMSGSTATUSMB6 : 1; //12 - unsigned NOTFULLSTATUSMB6 : 1; //13 - unsigned NEWMSGSTATUSMB7 : 1; //14 - unsigned NOTFULLSTATUSMB7 : 1; //15 - unsigned rsvd : 16; //31:16 - } ENABLE_CLR_bit; - }; - } IRQ[4]; - -} sysMailbox; - -#ifdef __GNUC__ -static volatile sysMailbox *__CT_MBX = (void *)0x480C8000; -#define CT_MBX (*__CT_MBX) -#else -volatile __far sysMailbox CT_MBX __attribute__((cregister("MBX0", far), peripheral)); -#endif - -#endif /* _SYS_MAILBOX_H_ */ diff --git a/lib/pru_rpmsg/include/am335x/sys_pwmss.h b/lib/pru_rpmsg/include/am335x/sys_pwmss.h deleted file mode 100644 index ee9036c0a5a0..000000000000 --- a/lib/pru_rpmsg/include/am335x/sys_pwmss.h +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * * Neither the name of Texas Instruments Incorporated nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _SYS_PWMSS_H_ -#define _SYS_PWMSS_H_ - -/* SYS_PWMSS register set */ -typedef struct { - - /***************************/ - /* PWM Subsystem Registers */ - /***************************/ - /* SYS_PWMSS_IDVER register bit field */ - union { - volatile uint32_t IDVER; - - volatile struct { - unsigned Y_MINOR : 6; //5:0 - unsigned CUSTOM : 2; //7:6 - unsigned X_MAJOR : 3; //10:8 - unsigned R_RTL : 5; //15:11 - unsigned FUNC : 12; //27:16 - unsigned rsvd28 : 2; //29:28 - unsigned SCHEME : 2; //31:30 - } IDVER_bit; - }; // 0x0 - - /* SYS_PWMSS_SYSCONFIG register bit field */ - union { - volatile uint32_t SYSCONFIG; - - volatile struct { - unsigned SOFTRESET : 1; //0 - unsigned FREEEMU : 1; //1 - unsigned IDLEMODE : 2; //3:2 - unsigned STANDBYMODE : 2; //5:4 - unsigned rsvd6 : 26; //31:6 - } SYSCONFIG_bit; - }; // 0x4 - - /* SYS_PWMSS_CLKCONFIG register bit field */ - union { - volatile uint32_t CLKCONFIG; - - volatile struct { - unsigned ECAPCLK_EN : 1; //0 - unsigned ECAPCLKSTOP_REQ : 1; //1 - unsigned rsvd2 : 2; //3:2 - unsigned EQEPCLK_EN : 1; //4 - unsigned EQEPCLKSTOP_REQ : 1; //5 - unsigned rsvd6 : 2; //7:6 - unsigned EPWMCLK_EN : 1; //8 - unsigned EPWMCLKSTOP_REQ : 1; //9 - unsigned rsvd10 : 22; //31:10 - } CLKCONFIG_bit; - }; // 0x8 - - /* SYS_PWMSS_CLKSTATUS register bit field */ - union { - volatile uint32_t CLKSTATUS; - - volatile struct { - unsigned ECAPCLK_EN_ACK : 1; //0 - unsigned ECAPCLKSTOP_ACK : 1; //1 - unsigned rsvd2 : 2; //3:2 - unsigned EQEPCLK_EN_ACK : 1; //4 - unsigned EQEPCLKSTOP_ACK : 1; //5 - unsigned rsvd6 : 2; //7:6 - unsigned EPWMCLK_EN_ACK : 1; //8 - unsigned EPWMCLKSTOP_ACK : 1; //9 - unsigned rsvd10 : 22; //31:10 - } CLKSTATUS_bit; - }; // 0xC - - uint32_t rsvd10[60]; // 0x10 - 0xFC - - /*************************/ - /* eCAP Module Registers */ - /*************************/ - /* SYS_PWMSS_ECAP_TSCTR register bit field */ - union { - volatile uint32_t ECAP_TSCTR; - - volatile struct { - unsigned TSCTR : 32; //31:0 - } ECAP_TSCTR_bit; - }; // 0x100 - - /* SYS_PWMSS_ECAP_CTRPHS register bit field */ - union { - volatile uint32_t ECAP_CTRPHS; - - volatile struct { - unsigned CTRPHS : 32; //31:0 - } ECAP_CTRPHS_bit; - }; // 0x104 - - /* SYS_PWMSS_ECAP_CAP1 register bit field */ - union { - volatile uint32_t ECAP_CAP1; - - volatile struct { - unsigned CAP1 : 32; //31:0 - } ECAP_CAP1_bit; - }; // 0x108 - - /* SYS_PWMSS_ECAP_CAP2 register bit field */ - union { - volatile uint32_t ECAP_CAP2; - - volatile struct { - unsigned CAP2 : 32; //31:0 - } ECAP_CAP2_bit; - }; // 0x10C - - /* SYS_PWMSS_ECAP_CAP3 register bit field */ - union { - volatile uint32_t ECAP_CAP3; - - volatile struct { - unsigned CAP3 : 32; //31:0 - } ECAP_CAP3_bit; - }; // 0x110 - - /* SYS_PWMSS_ECAP_CAP4 register bit field */ - union { - volatile uint32_t ECAP_CAP4; - - volatile struct { - unsigned CAP4 : 32; //31:0 - } ECAP_CAP4_bit; - }; // 0x114 - - uint32_t rsvd118[4]; // 0x118 - 0x124 - - /* SYS_PWMSS_ECAP_ECCTL1 register bit field */ - volatile uint16_t ECAP_ECCTL1; // 0x128 - - /* SYS_PWMSS_ECAP_ECCTL2 register bit field */ - volatile uint16_t ECAP_ECCTL2; // 0x12A - - /* SYS_PWMSS_ECAP_ECEINT register bit field */ - volatile uint16_t ECAP_ECEINT; // 0x12C - - /* SYS_PWMSS_ECAP_ECFLG register bit field */ - volatile uint16_t ECAP_ECFLG; // 0x12E - - /* SYS_PWMSS_ECAP_ECCLR register bit field */ - volatile uint16_t ECAP_ECCLR; // 0x130 - - /* SYS_PWMSS_ECAP_ECFRC register bit field */ - volatile uint16_t ECAP_ECFRC; // 0x132 - - uint32_t rsvd134[10]; // 0x134 - 0x158 - - /* SYS_PWMSS_ECAP_REVID register bit field */ - union { - volatile uint32_t ECAP_REVID; - - volatile struct { - unsigned REV : 32; //31:0 - } ECAP_REVID_bit; - }; // 0x15C - - uint32_t rsvd160[8]; // 0x160 - 0x17C - - /*************************/ - /* eQEP Module Registers */ - /*************************/ - /* SYS_PWMSS_EQEP_QPOSCNT register bit field */ - union { - volatile uint32_t EQEP_QPOSCNT; - - volatile struct { - unsigned QPOSCNT : 32; //31:0 - } EQEP_QPOSCNT_bit; - }; // 0x180 - - /* SYS_PWMSS_EQEP_QPOSINIT register bit field */ - union { - volatile uint32_t EQEP_QPOSINIT; - - volatile struct { - unsigned QPOSINIT : 32; //31:0 - } EQEP_QPOSINIT_bit; - }; // 0x184 - - /* SYS_PWMSS_EQEP_QPOSMAX register bit field */ - union { - volatile uint32_t EQEP_QPOSMAX; - - volatile struct { - unsigned QPOSMAX : 32; //31:0 - } EQEP_QPOSMAX_bit; - }; // 0x188 - - /* SYS_PWMSS_EQEP_QPOSCMP register bit field */ - union { - volatile uint32_t EQEP_QPOSCMP; - - volatile struct { - unsigned QPOSCMP : 32; //31:0 - } EQEP_QPOSCMP_bit; - }; // 0x18C - - /* SYS_PWMSS_EQEP_QPOSILAT register bit field */ - union { - volatile uint32_t EQEP_QPOSILAT; - - volatile struct { - unsigned QPOSILAT : 32; //31:0 - } EQEP_QPOSILAT_bit; - }; // 0x190 - - /* SYS_PWMSS_EQEP_QPOSSLAT register bit field */ - union { - volatile uint32_t EQEP_QPOSSLAT; - - volatile struct { - unsigned QPOSSLAT : 32; //31:0 - } EQEP_QPOSSLAT_bit; - }; // 0x194 - - /* SYS_PWMSS_EQEP_QPOSLAT register bit field */ - union { - volatile uint32_t EQEP_QPOSLAT; - - volatile struct { - unsigned QPOSLAT : 32; //31:0 - } EQEP_QPOSLAT_bit; - }; // 0x198 - - /* SYS_PWMSS_EQEP_QUTMR register bit field */ - union { - volatile uint32_t EQEP_QUTMR; - - volatile struct { - unsigned QUTMR : 32; //31:0 - } EQEP_QUTMR_bit; - }; // 0x19C - - /* SYS_PWMSS_EQEP_QUPRD register bit field */ - union { - volatile uint32_t EQEP_QUPRD; - - volatile struct { - unsigned QUPRD : 32; //31:0 - } EQEP_QUPRD_bit; - }; // 0x1A0 - - /* SYS_PWMSS_EQEP_QWDTMR register bit field */ - volatile uint16_t EQEP_QWDTMR; // 0x1A4 - - /* SYS_PWMSS_EQEP_QWDPRD register bit field */ - volatile uint16_t EQEP_QWDPRD; // 0x1A6 - - /* SYS_PWMSS_EQEP_QDECCTL register bit field */ - volatile uint16_t EQEP_QDECCTL; // 0x1A8 - - /* SYS_PWMSS_EQEP_QEPCTL register bit field */ - volatile uint16_t EQEP_QEPCTL; // 0x1AA - - /* SYS_PWMSS_EQEP_QCAPCTL register bit field */ - volatile uint16_t EQEP_QCAPCTL; // 0x1AC - - /* SYS_PWMSS_EQEP_QPOSCTL register bit field */ - volatile uint16_t EQEP_QPOSCTL; // 0x1AE - - /* SYS_PWMSS_EQEP_QEINT register bit field */ - volatile uint16_t EQEP_QEINT; // 0x1B0 - - /* SYS_PWMSS_EQEP_QFLG register bit field */ - volatile uint16_t EQEP_QFLG; // 0x1B2 - - /* SYS_PWMSS_EQEP_QCLR register bit field */ - volatile uint16_t EQEP_QCLR; // 0x1B4 - - /* SYS_PWMSS_EQEP_QFRC register bit field */ - volatile uint16_t EQEP_QFRC; // 0x1B6 - - /* SYS_PWMSS_EQEP_QEPSTS register bit field */ - volatile uint16_t EQEP_QEPSTS; // 0x1B8 - - /* SYS_PWMSS_EQEP_QCTMR register bit field */ - volatile uint16_t EQEP_QCTMR; // 0x1BA - - /* SYS_PWMSS_EQEP_QCPRD register bit field */ - volatile uint16_t EQEP_QCPRD; // 0x1BC - - /* SYS_PWMSS_EQEP_QCTMRLAT register bit field */ - volatile uint16_t EQEP_QCTMRLAT; // 0x1BE - - /* SYS_PWMSS_EQEP_QCPRDLAT register bit field */ - volatile uint16_t EQEP_QCPRDLAT; // 0x1C0 - - uint16_t rsvd1C2[1]; // 0x1C2 - 0x1C3 - uint32_t rsvd1C4[6]; // 0x1C4 - 0x1D8 - - /* SYS_PWMSS_EQEP_REVID register bit field */ - union { - volatile uint32_t EQEP_REVID; - - volatile struct { - unsigned REVID : 32; //31:0 - } EQEP_REVID_bit; - }; // 0x1DC - - uint32_t rsvd1E0[8]; // 0x1E0 - 0x1FC - - /*************************/ - /* ePWM Module Registers */ - /*************************/ - /* SYS_PWMSS_EPWM_TBCTL register bit field */ - volatile uint16_t EPWM_TBCTL; // 0x200 - - /* SYS_PWMSS_EPWM_TBSTS register bit field */ - volatile uint16_t EPWM_TBSTS; // 0x202 - - /* SYS_PWMSS_EPWM_TBPHSHR register bit field */ - volatile uint16_t EPWM_TBPHSHR; // 0x204 - - /* SYS_PWMSS_EPWM_TBPHS register bit field */ - volatile uint16_t EPWM_TBPHS; // 0x206 - - /* SYS_PWMSS_EPWM_TBCNT register bit field */ - volatile uint16_t EPWM_TBCNT; // 0x208 - - /* SYS_PWMSS_EPWM_TBPRD register bit field */ - volatile uint16_t EPWM_TBPRD; // 0x20A - - uint16_t rsvd20C[1]; // 0x20C - 0x20D - - /* SYS_PWMSS_EPWM_CMPCTL register bit field */ - volatile uint16_t EPWM_CMPCTL; // 0x20E - - /* SYS_PWMSS_EPWM_CMPAHR register bit field */ - volatile uint16_t EPWM_CMPAHR; // 0x210 - - /* SYS_PWMSS_EPWM_CMPA register bit field */ - volatile uint16_t EPWM_CMPA; // 0x212 - - /* SYS_PWMSS_EPWM_CMPB register bit field */ - volatile uint16_t EPWM_CMPB; // 0x214 - - /* SYS_PWMSS_EPWM_AQCTLA register bit field */ - volatile uint16_t EPWM_AQCTLA; // 0x216 - - /* SYS_PWMSS_EPWM_AQCTLB register bit field */ - volatile uint16_t EPWM_AQCTLB; // 0x218 - - /* SYS_PWMSS_EPWM_AQSFRC register bit field */ - volatile uint16_t EPWM_AQSFRC; // 0x21A - - /* SYS_PWMSS_EPWM_AQCSFRC register bit field */ - volatile uint16_t EPWM_AQCSFRC; // 0x21C - - /* SYS_PWMSS_EPWM_DBCTL register bit field */ - volatile uint16_t EPWM_DBCTL; // 0x21E - - /* SYS_PWMSS_EPWM_DBRED register bit field */ - volatile uint16_t EPWM_DBRED; // 0x220 - - /* SYS_PWMSS_EPWM_DBFED register bit field */ - volatile uint16_t EPWM_DBFED; // 0x222 - - /* SYS_PWMSS_EPWM_TZSEL register bit field */ - volatile uint16_t EPWM_TZSEL; // 0x224 - - uint16_t rsvd226[1]; // 0x226 - 0x227 - - /* SYS_PWMSS_EPWM_TZCTL register bit field */ - volatile uint16_t EPWM_TZCTL; // 0x228 - - /* SYS_PWMSS_EPWM_TZEINT register bit field */ - volatile uint16_t EPWM_TZEINT; // 0x22A - - /* SYS_PWMSS_EPWM_TZFLG register bit field */ - volatile uint16_t EPWM_TZFLG; // 0x22C - - /* SYS_PWMSS_EPWM_TZCLR register bit field */ - volatile uint16_t EPWM_TZCLR; // 0x22E - - /* SYS_PWMSS_EPWM_TZFRC register bit field */ - volatile uint16_t EPWM_TZFRC; // 0x230 - - /* SYS_PWMSS_EPWM_ETSEL register bit field */ - volatile uint16_t EPWM_ETSEL; // 0x232 - - /* SYS_PWMSS_EPWM_ETPS register bit field */ - volatile uint16_t EPWM_ETPS; // 0x234 - - /* SYS_PWMSS_EPWM_ETFLG register bit field */ - volatile uint16_t EPWM_ETFLG; // 0x236 - - /* SYS_PWMSS_EPWM_ETCLR register bit field */ - volatile uint16_t EPWM_ETCLR; // 0x238 - - /* SYS_PWMSS_EPWM_ETFRC register bit field */ - volatile uint16_t EPWM_ETFRC; // 0x23A - - /* SYS_PWMSS_EPWM_PCCTL register bit field */ - volatile uint16_t EPWM_PCCTL; // 0x23C - - uint16_t rsvd23E[1]; // 0x23E - 0x23F - uint32_t rsvd240[32]; // 0x240 - 0x2BC - - /* SYS_PWMSS_EPWM_HRCNGF register bit field */ - volatile uint16_t EPWM_HRCNGF; // 0x2C0 - -} sysPwmss; - -volatile __far sysPwmss PWMSS0 __attribute__((cregister("PWMSS0", far), peripheral)); -volatile __far sysPwmss PWMSS1 __attribute__((cregister("PWMSS1", far), peripheral)); -volatile __far sysPwmss PWMSS2 __attribute__((cregister("PWMSS2", far), peripheral)); - -#endif /* _SYS_PWMSS_H_ */ diff --git a/lib/pru_rpmsg/include/pru_rpmsg.h b/lib/pru_rpmsg/include/pru_rpmsg.h index 6f087207844a..5d6d65e24890 100644 --- a/lib/pru_rpmsg/include/pru_rpmsg.h +++ b/lib/pru_rpmsg/include/pru_rpmsg.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Texas Instruments Incorporated - http://www.ti.com/ + * Copyright (C) 2016-2018 Texas Instruments Incorporated - http://www.ti.com/ * * * Redistribution and use in source and binary forms, with or without @@ -84,6 +84,10 @@ #define RPMSG_NAME_SIZE 32 /* The maximum size of the buffer (including the header) */ #define RPMSG_BUF_SIZE 512 +/* The size of the buffer header */ +#define RPMSG_HEADER_SIZE 16 +/* The maximum size of the buffer message */ +#define RPMSG_MESSAGE_SIZE 496 enum pru_rpmsg_ns_flags { RPMSG_NS_CREATE = 0, diff --git a/lib/pru_rpmsg/include/pru_types.h b/lib/pru_rpmsg/include/pru_types.h index c8ab8b1ba8eb..191ae8984af3 100644 --- a/lib/pru_rpmsg/include/pru_types.h +++ b/lib/pru_rpmsg/include/pru_types.h @@ -1,6 +1,5 @@ /* - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * + * Copyright (C) 2015-2021 Texas Instruments Incorporated - http://www.ti.com/ * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -37,29 +36,53 @@ /* Custom Resource info: Must match drivers/remoteproc/pru_rproc.h */ #define TYPE_PRU_INTS 1 +#define PRU_INTS_VER0 (0 << 16) +#define PRU_INTS_VER1 (1 << 16) + /** - * struct ch_map - sysevts-to-channel mapping + * struct pruss_int_map - sysevt to channel to host interrupt mapping * - * @evt: the number of the sysevt - * @ch: channel number assigned to a given @sysevt + * @event: the number of the sysevt + * @chnl: channel number assigned to a given @event + * @host: the host interrupt assigned to a given @chnl * * PRU system events are mapped to channels, and these channels are mapped to * hosts. Events can be mapped to channels in a one-to-one or many-to-one ratio * (multiple events per channel), and channels can be mapped to hosts in a * one-to-one or many-to-one ratio (multiple events per channel). * - * @evt is the number of the sysevt, and @ch is the number of the channel to be - * mapped. + * @event is the number of the sysevt, @chnl is the number of the channel to be + * mapped, and @host is the number of the host interrupt to be mapped. + * + * pruss_int_map is defined in Linux at drivers/remoteproc/pru_rproc.h */ +struct pruss_int_map { + uint8_t event; + uint8_t chnl; + uint8_t host; +}; -struct ch_map { - uint8_t evt; - uint8_t ch; +/** + * struct pru_irq_rsc - PRU firmware section header for IRQ data + * @type: resource type + * @num_evts: number of described events + * @pru_intc_map: PRU interrupt routing description + * + * The PRU firmware blob can contain optional .pru_irq_map ELF section, which + * provides the PRUSS interrupt mapping description. The pru_irq_rsc struct + * describes resource entry format. + * + * pru_irq_rsc is defined in Linux at drivers/remoteproc/pru_rproc.h + */ +struct pru_irq_rsc { + uint8_t type; + uint8_t num_evts; + struct pruss_int_map pru_intc_map[]; }; /** * struct fw_rsc_custom_ints - custom resource to define PRU interrupts - * @version: revision number of the custom ints type + * @reserved: reserved field, value should be 0 for backward compatibility * @channel_host: assignment of PRU channels to hosts * @num_evts: device address of INTC * @event_channel: mapping of sysevts to channels @@ -74,10 +97,36 @@ struct ch_map { * specifies to which host, if any, a channel is mapped. */ struct fw_rsc_custom_ints { - uint16_t version; + uint16_t reserved; uint8_t channel_host[10]; uint32_t num_evts; struct ch_map *event_channel; }; +/** + * struct fw_rsc_custom_ints_k3 - custom resource to define PRU/RTU interrupts + * @channel_host: assignment of PRU interrupt channels to host interrupts + * @num_evts: number of mappings defined in the @event_channel map + * @event_channel: PRU device address of pointer to array of events to channel + * mappings + * + * PRU system events are mapped to channels, and these channels are mapped + * to host interrupts. Events can be mapped to channels in a one-to-one or + * many-to-one ratio (multiple events per channel), and channels can be + * mapped to host interrupts in a one-to-one or many-to-one ratio (multiple + * channels per interrupt). + * + * This structure needs to be used using custom interrupt resource version + * number 1. This structure is to be used with firmwares dealing with the + * additional host interrupts on ICSSG IP instances. The firmwares for PRU + * cores on ICSSG can get away with the standard version (if not dealing with + * Task Manager), but the firmwares for RTU cores would definitely need this + * for mapping to the corresponding higher host interrupts. + */ +struct fw_rsc_custom_ints_k3 { + uint8_t channel_host[20]; + uint32_t num_evts; + struct ch_map *event_channel; +}; + #endif /* _PRU_TYPES_H_ */ diff --git a/lib/pru_rpmsg/include/pru_virtio_ring.h b/lib/pru_rpmsg/include/pru_virtio_ring.h index 54817352b026..d17b68bf9ccb 100644 --- a/lib/pru_rpmsg/include/pru_virtio_ring.h +++ b/lib/pru_rpmsg/include/pru_virtio_ring.h @@ -109,25 +109,25 @@ struct vring { * struct vring_desc desc[num]; * * // A ring of available descriptor heads with free-running index. - * __u16 avail_flags; - * __u16 avail_idx; - * __u16 available[num]; - * __u16 used_event_idx; + * uint16_t avail_flags; + * uint16_t avail_idx; + * uint16_t available[num]; + * uint16_t used_event_idx; * * // Padding to the next align boundary. * char pad[]; * * // A ring of used descriptor heads with free-running index. - * __u16 used_flags; - * __u16 used_idx; + * uint16_t used_flags; + * uint16_t used_idx; * struct vring_used_elem used[num]; - * __u16 avail_event_idx; + * uint16_t avail_event_idx; * }; */ /* We publish the used event index at the end of the available ring, and vice * versa. They are at the end for backwards compatibility. */ #define vring_used_event(vr) ((vr)->avail->ring[(vr)->num]) -#define vring_avail_event(vr) (*(__u16 *)&(vr)->used->ring[(vr)->num]) +#define vring_avail_event(vr) (*(uint16_t *)&(vr)->used->ring[(vr)->num]) static inline void vring_init(struct vring *vr, uint32_t num, void *p, uint64_t align) diff --git a/lib/pru_rpmsg/include/rsc_types.h b/lib/pru_rpmsg/include/rsc_types.h index 755ab601bf97..a52f86c5a49e 100644 --- a/lib/pru_rpmsg/include/rsc_types.h +++ b/lib/pru_rpmsg/include/rsc_types.h @@ -1,5 +1,5 @@ /* - * Copyright(c) 2011 Texas Instruments, Inc. + * Copyright(c) 2011-2018 Texas Instruments, Inc. * Copyright(c) 2011 Google, Inc. * All rights reserved. * @@ -45,9 +45,14 @@ #define TYPE_DEVMEM 1 #define TYPE_TRACE 2 #define TYPE_VDEV 3 -#define TYPE_INTMEM 4 +#define TYPE_PRELOAD_VENDOR 4 +#define TYPE_POSTLOAD_VENDOR 5 +/* deprecated, define only for backward compatibility */ #define TYPE_CUSTOM 5 +/* Linux kernel defines this as (-1), below define avoids compile warnings */ +#define FW_RSC_ADDR_ANY (~0) + union fw_custom { /* add custom resources here */ struct fw_rsc_custom_ints pru_ints; @@ -321,25 +326,59 @@ struct fw_rsc_intmem { char name[32]; }; +/** + * struct fw_rsc_custom_hdr - header to be used with custom resource types + * @type: type of custom resource, value should be one of vendor resource types + * @u: union identifying the vendor/custom resource sub-type + * @sub_type: type to identify the custom resource + * @rsc_size: size of the specific custom resource structure (in bytes) + * + * This is a header structure to be used before any specific custom resource + * type. @type is one of the generic VENDOR types, the @u is an union of the + * overall @sub_type field which is made up of the custom resource version + * number in the upper 16-bits and the custom resource sub-type itself in the + * lower 16-bits. @rsc_size is the length of the actual custom resource sub-type + * (in bytes). These will be interpreted by the host-side device-specific + * driver. + */ +struct fw_rsc_custom_hdr { + uint32_t type; + union { + uint32_t sub_type; + struct { + uint16_t type; + uint16_t ver; + } st; + } u; + uint32_t rsc_size; +}; + /** * struct fw_rsc_custom - used for custom resource types - * @type: type of resource - * @sub_type: type of custom resource + * @type: type of resource, value should be one of vendor resource types + * @u: union identifying the type of vendor/custom resource * @rsc_size: size of @rsc (in bytes) * @rsc: the custom resource * * This resource allows for custom resources specific to an architecture or * device. * - * @type is the generic CUSTOM type, @sub_type is the specific custom resource, - * @rsc_size is the length of @rsc (in bytes), and @rsc is the actual - * parameters. These will be interpreted by the host-side device-specific + * @type is one of the generic VENDOR types, the @u is an union of the + * overall @sub_type field which is made up of the custom resource version + * number in the upper 16-bits and the custom resource type itself in the + * lower 16-bits. @rsc_size is the length of @rsc (in bytes), and @rsc is the + * actual parameters. These will be interpreted by the host-side device-specific * driver. */ - struct fw_rsc_custom { uint32_t type; - uint32_t sub_type; + union { + uint32_t sub_type; + struct { + uint16_t type; + uint16_t ver; + } st; + } u; uint32_t rsc_size; union fw_custom rsc; }; diff --git a/lib/pru_rpmsg/include/types.h b/lib/pru_rpmsg/include/types.h deleted file mode 100644 index 35acdcf696d2..000000000000 --- a/lib/pru_rpmsg/include/types.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * types.h - standard redefined types - */ - -#ifndef _TYPES_H_ -#define _TYPES_H_ - -typedef uint8_t __u8; -typedef uint16_t __u16; -typedef uint32_t __u32; -typedef uint64_t __u64; - -typedef uint8_t u8; -typedef uint16_t u16; -typedef uint32_t u32; -typedef uint64_t u64; - -#endif /* _TYPES_H_ */ diff --git a/lib/pru_rpmsg/pru_virtqueue.c b/lib/pru_rpmsg/pru_virtqueue.c index 5b14c6e4957b..cce93d01fede 100644 --- a/lib/pru_rpmsg/pru_virtqueue.c +++ b/lib/pru_rpmsg/pru_virtqueue.c @@ -41,10 +41,10 @@ */ #include -#ifndef __GNUC__ -volatile register uint32_t __R31; -#else +#ifdef __GNUC__ #include +#else +volatile register uint32_t __R31; #endif /* bit 5 is the valid strobe to generate system events with __R31 */ @@ -131,19 +131,12 @@ int16_t pru_virtqueue_kick( struct pru_virtqueue *vq ) { - unsigned int r31; - /* If requested, do not kick the ARM host */ if (vq->vring.avail->flags & VRING_AVAIL_F_NO_INTERRUPT) return PRU_VIRTQUEUE_NO_KICK; /* Generate a system event to kick the ARM */ - r31 = (INT_ENABLE | (vq->to_arm_event - INT_OFFSET)); -#ifdef __GNUC__ - write_r31(r31); -#else - __R31 = r31; -#endif + __R31 = (INT_ENABLE | (vq->to_arm_event - INT_OFFSET)); return PRU_VIRTQUEUE_SUCCESS; } diff --git a/scripts/install-beaglebone.sh b/scripts/install-beaglebone.sh index a68c8d060397..58424c05fc7a 100755 --- a/scripts/install-beaglebone.sh +++ b/scripts/install-beaglebone.sh @@ -1,5 +1,5 @@ #!/bin/bash -# This script installs Klipper on a Beaglebone running Debian Jessie +# This script installs Klipper on a Beaglebone running Debian Bullseye # for use with its PRU micro-controller. # Step 1: Do main install @@ -13,6 +13,12 @@ install_main() # Step 2: Install additional system packages install_packages() { + # Remove conflicting AVR packages + PKGLIST_REMOVE="avrdude gcc-avr binutils-avr avr-libc" + + report_status "Removing ARM packages because of conflicts with PRU packages" + sudo apt-get remove --yes ${PKGLIST_REMOVE} + # Install desired packages PKGLIST="gcc-pru" @@ -25,7 +31,7 @@ install_script() { report_status "Installing pru start script..." sudo cp "${SRCDIR}/scripts/klipper-pru-start.sh" /etc/init.d/klipper_pru - sudo update-rc.d klipper_pru defaults + sudo update-rc.d klipper_pru defaults-disabled } # Step 4: Install pru udev rule @@ -33,6 +39,7 @@ install_udev() { report_status "Installing pru udev rule..." sudo /bin/sh -c "cat > /etc/udev/rules.d/pru.rules" <; + * interrupts = <21 2 2>, <22 3 3>; + * interrupt-names = "interrupt_name1", "interrupt_name2"; + * }; + * + */ + +#include + +/* + * .pru_irq_map is used by the RemoteProc driver during initialization. However, + * the map is NOT used by the PRU firmware. That means DATA_SECTION and RETAIN + * are required to prevent the PRU compiler from optimizing out .pru_irq_map. + */ + +#if !defined(__GNUC__) + #pragma DATA_SECTION(my_irq_rsc, ".pru_irq_map") + #pragma RETAIN(my_irq_rsc) + #define __pru_irq_map +#else + #define __pru_irq_map __attribute__((section(".pru_irq_map"), \ + unavailable("pru_irq_map is for usage by the host only"))) +#endif + +struct pru_irq_rsc my_irq_rsc __pru_irq_map = { + 0, /* type = 0 */ + 4, /* number of system events being mapped */ + { +// this item (sysevt=16) is obsolete in Linux 5.10 +// {16, 2, 2}, /* {sysevt, channel, host interrupt} */ + {17, 0, 0}, /* {sysevt, channel, host interrupt} */ + {18, 0, 0}, /* {sysevt, channel, host interrupt} */ + {19, 1, 1}, /* {sysevt, channel, host interrupt} */ +// next item is responsible for timer to function, +// it should be kept last in Linux 5.10 +// (if it will be first - gpios will not work) + {7, 1, 1}, /* {sysevt, channel, host interrupt} */ + }, +}; + +#endif /* _INTC_MAP_0_H_ */ diff --git a/src/pru/internal.h b/src/pru/internal.h index 2aef0da3c624..f735f80af950 100644 --- a/src/pru/internal.h +++ b/src/pru/internal.h @@ -15,6 +15,12 @@ #define WAKE_PRU1_IRQ 1 #define WAKE_ARM_IRQ 2 +/* Host-0 Interrupt sets bit 31 in register R31 */ +#define HOST_INT_0 ((uint32_t) 1 << 30) + +/* Host-1 Interrupt sets bit 31 in register R31 */ +#define HOST_INT_1 ((uint32_t) 1 << 31) + #define R31_IRQ_OFFSET 30 #define R31_WRITE_IRQ_SELECT (1<<5) diff --git a/src/pru/main.c b/src/pru/main.c index 27634a12b525..2127479f4b60 100644 --- a/src/pru/main.c +++ b/src/pru/main.c @@ -7,7 +7,7 @@ #include // uint32_t #include // read_r31 #include // CT_IEP -#include // CT_INTC + #include // resource_table #include "board/misc.h" // dynmem_start #include "board/io.h" // readl @@ -75,7 +75,7 @@ timer_kick(void) timer_set(timer_read_time() + 50); CT_IEP.TMR_CMP_STS = 0xff; __delay_cycles(4); - CT_INTC.SECR0 = 1 << IEP_EVENT; + PRU_INTC.SECR0 = 1 << IEP_EVENT; } static uint32_t in_timer_dispatch; @@ -83,9 +83,9 @@ static uint32_t in_timer_dispatch; static void _irq_poll(void) { - uint32_t secr0 = CT_INTC.SECR0; + uint32_t secr0 = PRU_INTC.SECR0; if (secr0 & (1 << KICK_PRU1_EVENT)) { - CT_INTC.SECR0 = 1 << KICK_PRU1_EVENT; + PRU_INTC.SECR0 = 1 << KICK_PRU1_EVENT; sched_wake_tasks(); } if (secr0 & (1 << IEP_EVENT)) { @@ -93,7 +93,7 @@ _irq_poll(void) in_timer_dispatch = 1; uint32_t next = timer_dispatch_many(); timer_set(next); - CT_INTC.SECR0 = 1 << IEP_EVENT; + PRU_INTC.SECR0 = 1 << IEP_EVENT; in_timer_dispatch = 0; } } @@ -148,7 +148,7 @@ console_sendf(const struct command_encoder *ce, va_list args) SHARED_MEM->next_encoder_args = args; writel(&SHARED_MEM->next_encoder, (uint32_t)ce); - // Signal PRU0 to transmit message + // Signal PRU0 to transmit message - 20 | (18-16) = 22 = 0010 0010 write_r31(R31_WRITE_IRQ_SELECT | (KICK_PRU0_EVENT - R31_WRITE_IRQ_OFFSET)); uint32_t itd = in_timer_dispatch; while (readl(&SHARED_MEM->next_encoder)) @@ -197,25 +197,6 @@ dynmem_end(void) return (void*)(8*1024 - STACK_SIZE); } - -/**************************************************************** - * Resource table - ****************************************************************/ - -struct my_resource_table { - struct resource_table base; - - uint32_t offset[1]; /* Should match 'num' in actual definition */ -} resourceTable __visible __section(".resource_table") = { - { - 1, /* Resource table version: only version 1 is - * supported by the current driver */ - 0, /* number of entries in the table */ - { 0, 0 }, /* reserved, must be zero */ - }, -}; - - /**************************************************************** * Startup ****************************************************************/ diff --git a/src/pru/pru0.c b/src/pru/pru0.c index efede04466f0..57d55d2792f7 100644 --- a/src/pru/pru0.c +++ b/src/pru/pru0.c @@ -8,17 +8,16 @@ #include // uint32_t #include // memset #include // write_r31 -#include // CT_CFG -#include // CT_INTC #include // pru_rpmsg_send #include // VIRTIO_ID_RPMSG -#include // resource_table #include "board/io.h" // readl #include "board/misc.h" // console_sendf #include "command.h" // command_encode_add_frame #include "compiler.h" // __section #include "internal.h" // SHARED_MEM #include "sched.h" // sched_shutdown +#include "intc_map_0.h" +#include "resource_table.h" struct pru_rpmsg_transport transport; static uint16_t transport_dst; @@ -32,8 +31,7 @@ static uint16_t transport_dst; #define CHAN_DESC "Channel 30" #define CHAN_PORT 30 -#define RPMSG_HDR_SIZE 16 -static uint8_t transmit_buf[RPMSG_BUF_SIZE - RPMSG_HDR_SIZE]; +static uint8_t transmit_buf[RPMSG_MESSAGE_SIZE]; static int transmit_pos; // Transmit all pending message blocks @@ -195,7 +193,7 @@ static void process_io(void) { for (;;) { - CT_INTC.SECR0 = ((1 << KICK_PRU0_FROM_ARM_EVENT) + PRU_INTC.SECR0 = ((1 << KICK_PRU0_FROM_ARM_EVENT) | (1 << KICK_PRU0_EVENT)); check_can_send(); int can_sleep = check_can_read(); @@ -251,112 +249,6 @@ console_sendf(const struct command_encoder *ce, va_list args) } -/**************************************************************** - * Resource table - ****************************************************************/ - -/* - * Sizes of the virtqueues (expressed in number of buffers supported, - * and must be power of 2) - */ -#define PRU_RPMSG_VQ0_SIZE 16 -#define PRU_RPMSG_VQ1_SIZE 16 - -/* - * The feature bitmap for virtio rpmsg - */ -#define VIRTIO_RPMSG_F_NS 0 //name service notifications - -/* This firmware supports name service notifications as one of its features */ -#define RPMSG_PRU_C0_FEATURES (1 << VIRTIO_RPMSG_F_NS) - -/* Definition for unused interrupts */ -#define HOST_UNUSED 255 - -/* Mapping sysevts to a channel. Each pair contains a sysevt, channel. */ -static struct ch_map pru_intc_map[] = { - {IEP_EVENT, WAKE_PRU1_IRQ}, - {KICK_ARM_EVENT, WAKE_ARM_IRQ}, - {KICK_PRU0_FROM_ARM_EVENT, WAKE_PRU0_IRQ}, - {KICK_PRU0_EVENT, WAKE_PRU0_IRQ}, - {KICK_PRU1_EVENT, WAKE_PRU1_IRQ}, -}; - -struct my_resource_table { - struct resource_table base; - - uint32_t offset[2]; /* Should match 'num' in actual definition */ - - /* rpmsg vdev entry */ - struct fw_rsc_vdev rpmsg_vdev; - struct fw_rsc_vdev_vring rpmsg_vring0; - struct fw_rsc_vdev_vring rpmsg_vring1; - - /* intc definition */ - struct fw_rsc_custom pru_ints; -} resourceTable __section(".resource_table") = { - { - 1, /* Resource table version: only version 1 is - * supported by the current driver */ - 2, /* number of entries in the table */ - { 0, 0 }, /* reserved, must be zero */ - }, - /* offsets to entries */ - { - offsetof(struct my_resource_table, rpmsg_vdev), - offsetof(struct my_resource_table, pru_ints), - }, - - /* rpmsg vdev entry */ - { - (uint32_t)TYPE_VDEV, //type - (uint32_t)VIRTIO_ID_RPMSG, //id - (uint32_t)0, //notifyid - (uint32_t)RPMSG_PRU_C0_FEATURES,//dfeatures - (uint32_t)0, //gfeatures - (uint32_t)0, //config_len - (uint8_t)0, //status - (uint8_t)2, //num_of_vrings, only two is supported - {(uint8_t)0, (uint8_t)0 }, //reserved - /* no config data */ - }, - /* the two vrings */ - { - 0, //da, will be populated by host, can't pass it in - 16, //align (bytes), - PRU_RPMSG_VQ0_SIZE, //num of descriptors - 0, //notifyid, will be populated, can't pass right now - 0 //reserved - }, - { - 0, //da, will be populated by host, can't pass it in - 16, //align (bytes), - PRU_RPMSG_VQ1_SIZE, //num of descriptors - 0, //notifyid, will be populated, can't pass right now - 0 //reserved - }, - - { - TYPE_CUSTOM, TYPE_PRU_INTS, - sizeof(struct fw_rsc_custom_ints), - { /* PRU_INTS version */ - { - 0x0000, - /* Channel-to-host mapping, 255 for unused */ - { - WAKE_PRU0_IRQ, WAKE_PRU1_IRQ, WAKE_ARM_IRQ, - HOST_UNUSED, HOST_UNUSED, HOST_UNUSED, - HOST_UNUSED, HOST_UNUSED, HOST_UNUSED, HOST_UNUSED - }, - /* Number of evts being mapped to channels */ - (sizeof(pru_intc_map) / sizeof(struct ch_map)), - /* Pointer to the structure containing mapped events */ - pru_intc_map, - }, - }, - }, -}; - /**************************************************************** * Startup @@ -368,11 +260,26 @@ int main(void) { // allow access to external memory +#if defined(__AM335X__) + /* AM335x must enable OCP master port access in order for the PRU to + * read external memories.*/ CT_CFG.SYSCFG_bit.STANDBY_INIT = 0; +#endif + + /* Clear the status of the PRU-ICSS system event that the ARM + will use to 'kick' us */ +#if defined(__AM335X__) + PRU_INTC.SICR_bit.STS_CLR_IDX = KICK_PRU0_FROM_ARM_EVENT; +#elif defined(__TDA4VM__) || defined(__AM62X__) + PRU_INTC.STATUS_CLR_INDEX_REG_bit.STATUS_CLR_INDEX = \ + KICK_PRU0_FROM_ARM_EVENT; +#else + #error "Unsupported SoC." +#endif // clear all irqs - CT_INTC.SECR0 = 0xffffffff; - CT_INTC.SECR1 = 0xffffffff; + PRU_INTC.SECR0 = 0xffffffff; + PRU_INTC.SECR1 = 0xffffffff; /* Make sure the Linux drivers are ready for RPMsg communication */ volatile uint8_t *status = &resourceTable.rpmsg_vdev.status; diff --git a/src/pru/resource_table.h b/src/pru/resource_table.h new file mode 100644 index 000000000000..9dff2b9c8541 --- /dev/null +++ b/src/pru/resource_table.h @@ -0,0 +1,91 @@ +/**************************************************************** + * Resource table + ****************************************************************/ + +#ifndef _RESOURCE_TABLE_H_ +#define _RESOURCE_TABLE_H_ + +#include +#include "pru_virtio_ids.h" + +/* + * Sizes of the virtqueues (expressed in number of buffers supported, + * and must be power of 2) + */ +#define PRU_RPMSG_VQ0_SIZE 16 +#define PRU_RPMSG_VQ1_SIZE 16 + +/* + * The feature bitmap for virtio rpmsg + */ +#define VIRTIO_RPMSG_F_NS 0 //name service notifications + +/* This firmware supports name service notifications as one of its features */ +#define RPMSG_PRU_C0_FEATURES (1 << VIRTIO_RPMSG_F_NS) + +/* Definition for unused interrupts */ +#define HOST_UNUSED 255 + +struct my_resource_table { + struct resource_table base; + + uint32_t offset[1]; /* Should match 'num' in actual definition */ + + /* rpmsg vdev entry */ + struct fw_rsc_vdev rpmsg_vdev; + struct fw_rsc_vdev_vring rpmsg_vring0; + struct fw_rsc_vdev_vring rpmsg_vring1; + +}; + +#if !defined(__GNUC__) + #pragma DATA_SECTION(resourceTable, ".resource_table") + #pragma RETAIN(resourceTable) + #define __resource_table /* */ +#else + #define __resource_table __attribute__((section(".resource_table"))) +#endif + +struct my_resource_table resourceTable __resource_table = { + { + 1, /* Resource table version: only version 1 is + * supported by the current driver */ + 1, /* number of entries in the table */ + {0, 0}, /* reserved, must be zero */ + }, + /* offsets to entries */ + { + offsetof(struct my_resource_table, rpmsg_vdev), + }, + + /* rpmsg vdev entry */ + { + (uint32_t)TYPE_VDEV, //type + (uint32_t)VIRTIO_ID_RPMSG, //id + (uint32_t)0, //notifyid + (uint32_t)RPMSG_PRU_C0_FEATURES,//dfeatures + (uint32_t)0, //gfeatures + (uint32_t)0, //config_len + (uint8_t)0, //status + (uint8_t)2, //num_of_vrings, only two is supported + {(uint8_t)0, (uint8_t)0 }, //reserved + /* no config data */ + }, + /* the two vrings */ + { + FW_RSC_ADDR_ANY, //da, will be populated by host, can't pass it in + 16, //align (bytes), + PRU_RPMSG_VQ0_SIZE, //num of descriptors + 0, //notifyid, will be populated, can't pass right now + 0 //reserved + }, + { + FW_RSC_ADDR_ANY, //da, will be populated by host, can't pass it in + 16, //align (bytes), + PRU_RPMSG_VQ1_SIZE, //num of descriptors + 0, //notifyid, will be populated, can't pass right now + 0 //reserved + }, +}; + +#endif /* _RESOURCE_TABLE_H_ */ From 7b490f3ec1d3f0477b90c74d96f51ba1fb457a46 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 27 Apr 2024 11:10:01 -0400 Subject: [PATCH 107/190] probe: Fix typo in activate/deactive error messages Signed-off-by: Kevin O'Connor --- klippy/extras/probe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index c275d4aaef93..073c875cc3ea 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -330,14 +330,14 @@ def _raise_probe(self): self.deactivate_gcode.run_gcode_from_command() if toolhead.get_position()[:3] != start_pos[:3]: raise self.printer.command_error( - "Toolhead moved during probe activate_gcode script") + "Toolhead moved during probe deactivate_gcode script") def _lower_probe(self): toolhead = self.printer.lookup_object('toolhead') start_pos = toolhead.get_position() self.activate_gcode.run_gcode_from_command() if toolhead.get_position()[:3] != start_pos[:3]: raise self.printer.command_error( - "Toolhead moved during probe deactivate_gcode script") + "Toolhead moved during probe activate_gcode script") def multi_probe_begin(self): if self.stow_on_each_sample: return From d8d072b3759a2b62de37efd4b8811d962a4d7e7c Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 29 Apr 2024 12:07:28 -0400 Subject: [PATCH 108/190] adxl345: Fix read_axes_map() for non-adxl345 accelerometers Commit 3f845019 unified the reading of the axes_map configuration variable, but broke the per-sensor scaling capabilities. Pass the scale parameters to read_axes_map() so that it can be implemented per-sensor. Reported by @Neko-vecter. Signed-off-by: Kevin O'Connor --- klippy/extras/adxl345.py | 8 ++++---- klippy/extras/lis2dw.py | 2 +- klippy/extras/mpu9250.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index 5323be797bd4..bbc9e32b86cb 100644 --- a/klippy/extras/adxl345.py +++ b/klippy/extras/adxl345.py @@ -176,9 +176,9 @@ def cmd_ACCELEROMETER_DEBUG_WRITE(self, gcmd): self.chip.set_reg(reg, val) # Helper to read the axes_map parameter from the config -def read_axes_map(config): - am = {'x': (0, SCALE_XY), 'y': (1, SCALE_XY), 'z': (2, SCALE_Z), - '-x': (0, -SCALE_XY), '-y': (1, -SCALE_XY), '-z': (2, -SCALE_Z)} +def read_axes_map(config, scale_x, scale_y, scale_z): + am = {'x': (0, scale_x), 'y': (1, scale_y), 'z': (2, scale_z), + '-x': (0, -scale_x), '-y': (1, -scale_y), '-z': (2, -scale_z)} axes_map = config.getlist('axes_map', ('x','y','z'), count=3) if any([a not in am for a in axes_map]): raise config.error("Invalid axes_map parameter") @@ -191,7 +191,7 @@ class ADXL345: def __init__(self, config): self.printer = config.get_printer() AccelCommandHelper(config, self) - self.axes_map = read_axes_map(config) + self.axes_map = read_axes_map(config, SCALE_XY, SCALE_XY, SCALE_Z) self.data_rate = config.getint('rate', 3200) if self.data_rate not in QUERY_RATES: raise config.error("Invalid rate parameter: %d" % (self.data_rate,)) diff --git a/klippy/extras/lis2dw.py b/klippy/extras/lis2dw.py index 3f17c1f4cf28..45c12ea1aba1 100644 --- a/klippy/extras/lis2dw.py +++ b/klippy/extras/lis2dw.py @@ -37,7 +37,7 @@ class LIS2DW: def __init__(self, config): self.printer = config.get_printer() adxl345.AccelCommandHelper(config, self) - self.axes_map = adxl345.read_axes_map(config) + self.axes_map = adxl345.read_axes_map(config, SCALE, SCALE, SCALE) self.data_rate = 1600 # Setup mcu sensor_lis2dw bulk query code self.spi = bus.MCU_SPI_from_config(config, 3, default_speed=5000000) diff --git a/klippy/extras/mpu9250.py b/klippy/extras/mpu9250.py index 9a07b705e1be..ff15fed41be6 100644 --- a/klippy/extras/mpu9250.py +++ b/klippy/extras/mpu9250.py @@ -59,7 +59,7 @@ class MPU9250: def __init__(self, config): self.printer = config.get_printer() adxl345.AccelCommandHelper(config, self) - self.axes_map = adxl345.read_axes_map(config) + self.axes_map = adxl345.read_axes_map(config, SCALE, SCALE, SCALE) self.data_rate = config.getint('rate', 4000) if self.data_rate not in SAMPLE_RATE_DIVS: raise config.error("Invalid rate parameter: %d" % (self.data_rate,)) From af149b47814c500fb795d7d18df26643bc59412d Mon Sep 17 00:00:00 2001 From: charminULTRA Date: Mon, 29 Apr 2024 19:48:10 -0400 Subject: [PATCH 109/190] docs: Update Measuring_Resonances.md (#6509) Current command, using the *, results in bad chart output when more than one .csv file exists in the tmp folder. This isn't obvious for people who may not know that the * is a wildcard character. Signed-off-by: Jonathan Williams --- docs/Measuring_Resonances.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/Measuring_Resonances.md b/docs/Measuring_Resonances.md index c06f17a582aa..d5f7f54cc537 100644 --- a/docs/Measuring_Resonances.md +++ b/docs/Measuring_Resonances.md @@ -450,7 +450,11 @@ TEST_RESONANCES AXIS=Y ``` This will generate 2 CSV files (`/tmp/resonances_x_*.csv` and `/tmp/resonances_y_*.csv`). These files can be processed with the stand-alone -script on a Raspberry Pi. To do that, run the following commands: +script on a Raspberry Pi. This script is intended to be run with a single CSV +file for each axis measured, although it can be used with multiple CSV files +if you desire to average the results. Averaging results can be useful, for +example, if resonance tests were done at multiple test points. Delete the extra +CSV files if you do not desire to average them. ``` ~/klipper/scripts/calibrate_shaper.py /tmp/resonances_x_*.csv -o /tmp/shaper_calibrate_x.png ~/klipper/scripts/calibrate_shaper.py /tmp/resonances_y_*.csv -o /tmp/shaper_calibrate_y.png From 7e8c7f46a971c0aaae39754b52d52c001f052723 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Sun, 28 Apr 2024 12:59:23 +0200 Subject: [PATCH 110/190] klippy: Replace logging.warn usage with logging.warning logging.warn is an alias to logging.warning since Python 3.3 and will be removed in Python 3.13. Signed-off-by: Jelle van der Waa --- klippy/configfile.py | 2 +- klippy/serialhdl.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/klippy/configfile.py b/klippy/configfile.py index b93a23d98fd8..91b555cdef75 100644 --- a/klippy/configfile.py +++ b/klippy/configfile.py @@ -316,7 +316,7 @@ def log_config(self, config): self.printer.set_rollover_info("config", "\n".join(lines)) # Status reporting def runtime_warning(self, msg): - logging.warn(msg) + logging.warning(msg) res = {'type': 'runtime_warning', 'message': msg} self.runtime_warnings.append(res) self.status_warnings = self.runtime_warnings + self.deprecate_warnings diff --git a/klippy/serialhdl.py b/klippy/serialhdl.py index f9e542d35029..6aee564814d9 100644 --- a/klippy/serialhdl.py +++ b/klippy/serialhdl.py @@ -166,7 +166,8 @@ def connect_pipe(self, filename): try: fd = os.open(filename, os.O_RDWR | os.O_NOCTTY) except OSError as e: - logging.warn("%sUnable to open port: %s", self.warn_prefix, e) + logging.warning("%sUnable to open port: %s", + self.warn_prefix, e) self.reactor.pause(self.reactor.monotonic() + 5.) continue serial_dev = os.fdopen(fd, 'rb+', 0) @@ -187,7 +188,7 @@ def connect_uart(self, serialport, baud, rts=True): serial_dev.rts = rts serial_dev.open() except (OSError, IOError, serial.SerialException) as e: - logging.warn("%sUnable to open serial port: %s", + logging.warning("%sUnable to open serial port: %s", self.warn_prefix, e) self.reactor.pause(self.reactor.monotonic() + 5.) continue @@ -291,13 +292,13 @@ def _handle_unknown_init(self, params): logging.debug("%sUnknown message %d (len %d) while identifying", self.warn_prefix, params['#msgid'], len(params['#msg'])) def handle_unknown(self, params): - logging.warn("%sUnknown message type %d: %s", + logging.warning("%sUnknown message type %d: %s", self.warn_prefix, params['#msgid'], repr(params['#msg'])) def handle_output(self, params): logging.info("%s%s: %s", self.warn_prefix, params['#name'], params['#msg']) def handle_default(self, params): - logging.warn("%sgot %s", self.warn_prefix, params) + logging.warning("%sgot %s", self.warn_prefix, params) # Class to send a query command and return the received response class SerialRetryCommand: From 434770eaf9d382cccba203a3475ace2dc0653d7b Mon Sep 17 00:00:00 2001 From: "Donald A. Cupp Jr" Date: Fri, 3 May 2024 11:30:45 -0600 Subject: [PATCH 111/190] stm32: Add new spi2 on stm32g0 chips (#6569) Signed-off-by: Donald A. Cupp Jr --- src/stm32/spi.c | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/stm32/spi.c b/src/stm32/spi.c index 709055c2ac4f..648e9bcd0639 100644 --- a/src/stm32/spi.c +++ b/src/stm32/spi.c @@ -12,7 +12,7 @@ struct spi_info { SPI_TypeDef *spi; - uint8_t miso_pin, mosi_pin, sck_pin, function; + uint8_t miso_pin, mosi_pin, sck_pin, miso_af, mosi_af, sck_af; }; DECL_ENUMERATION("spi_bus", "spi2", 0); @@ -21,6 +21,7 @@ DECL_ENUMERATION("spi_bus", "spi1", 1); DECL_CONSTANT_STR("BUS_PINS_spi1", "PA6,PA7,PA5"); DECL_ENUMERATION("spi_bus", "spi1a", 2); DECL_CONSTANT_STR("BUS_PINS_spi1a", "PB4,PB5,PB3"); + #if CONFIG_MACH_STM32G4 DECL_ENUMERATION("spi_bus", "spi2_PA10_PA11_PF1", 3); DECL_CONSTANT_STR("BUS_PINS_spi2_PA10_PA11_PF1", "PA10,PA11,PF1"); @@ -42,33 +43,44 @@ DECL_CONSTANT_STR("BUS_PINS_spi1a", "PB4,PB5,PB3"); DECL_CONSTANT_STR("BUS_PINS_spi2b", "PI2,PI3,PI1"); #endif #endif + #if CONFIG_MACH_STM32G0B1 + DECL_ENUMERATION("spi_bus", "spi2_PB2_PB11_PB10", 5); + DECL_CONSTANT_STR("BUS_PINS_spi2_PB2_PB11_PB10", "PB2,PB11,PB10"); +#endif #endif +#define GPIO_FUNCTION_ALL(fn) GPIO_FUNCTION(fn), \ + GPIO_FUNCTION(fn), GPIO_FUNCTION(fn) + #if CONFIG_MACH_STM32F0 || CONFIG_MACH_STM32G0 - #define SPI_FUNCTION GPIO_FUNCTION(0) + #define SPI_FUNCTION_ALL GPIO_FUNCTION_ALL(0) #else - #define SPI_FUNCTION GPIO_FUNCTION(5) + #define SPI_FUNCTION_ALL GPIO_FUNCTION_ALL(5) #endif static const struct spi_info spi_bus[] = { - { SPI2, GPIO('B', 14), GPIO('B', 15), GPIO('B', 13), SPI_FUNCTION }, - { SPI1, GPIO('A', 6), GPIO('A', 7), GPIO('A', 5), SPI_FUNCTION }, - { SPI1, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION }, + { SPI2, GPIO('B', 14), GPIO('B', 15), GPIO('B', 13), SPI_FUNCTION_ALL }, + { SPI1, GPIO('A', 6), GPIO('A', 7), GPIO('A', 5), SPI_FUNCTION_ALL }, + { SPI1, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), SPI_FUNCTION_ALL }, #if CONFIG_MACH_STM32G4 - { SPI2, GPIO('A', 10), GPIO('A', 11), GPIO('F', 1), SPI_FUNCTION }, + { SPI2, GPIO('A', 10), GPIO('A', 11), GPIO('F', 1), SPI_FUNCTION_ALL }, #else - { SPI2, GPIO('C', 2), GPIO('C', 3), GPIO('B', 10), SPI_FUNCTION }, + { SPI2, GPIO('C', 2), GPIO('C', 3), GPIO('B', 10), SPI_FUNCTION_ALL }, #endif #ifdef SPI3 - { SPI3, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), GPIO_FUNCTION(6) }, + { SPI3, GPIO('B', 4), GPIO('B', 5), GPIO('B', 3), GPIO_FUNCTION_ALL(6) }, #if CONFIG_MACH_STM32F4 || CONFIG_MACH_STM32G4 - { SPI3, GPIO('C', 11), GPIO('C', 12), GPIO('C', 10), GPIO_FUNCTION(6) }, + { SPI3, GPIO('C', 11), GPIO('C', 12), GPIO('C', 10), GPIO_FUNCTION_ALL(6) }, #ifdef SPI4 - { SPI4, GPIO('E', 13), GPIO('E', 14), GPIO('E', 12), GPIO_FUNCTION(5) }, + { SPI4, GPIO('E', 13), GPIO('E', 14), GPIO('E', 12), GPIO_FUNCTION_ALL(5) }, #elif defined(GPIOI) - { SPI2, GPIO('I', 2), GPIO('I', 3), GPIO('I', 1), GPIO_FUNCTION(5) }, + { SPI2, GPIO('I', 2), GPIO('I', 3), GPIO('I', 1), GPIO_FUNCTION_ALL(5) }, #endif #endif + #if CONFIG_MACH_STM32G0B1 + { SPI2, GPIO('B', 2), GPIO('B', 11), GPIO('B', 10), + GPIO_FUNCTION(1), GPIO_FUNCTION(0), GPIO_FUNCTION(5) }, + #endif #endif }; @@ -82,9 +94,9 @@ spi_setup(uint32_t bus, uint8_t mode, uint32_t rate) SPI_TypeDef *spi = spi_bus[bus].spi; if (!is_enabled_pclock((uint32_t)spi)) { enable_pclock((uint32_t)spi); - gpio_peripheral(spi_bus[bus].miso_pin, spi_bus[bus].function, 1); - gpio_peripheral(spi_bus[bus].mosi_pin, spi_bus[bus].function, 0); - gpio_peripheral(spi_bus[bus].sck_pin, spi_bus[bus].function, 0); + gpio_peripheral(spi_bus[bus].miso_pin, spi_bus[bus].miso_af, 1); + gpio_peripheral(spi_bus[bus].mosi_pin, spi_bus[bus].mosi_af, 0); + gpio_peripheral(spi_bus[bus].sck_pin, spi_bus[bus].sck_af, 0); // Configure CR2 on stm32 f0/f7/g0/l4/g4 #if CONFIG_MACH_STM32F0 || CONFIG_MACH_STM32F7 || \ From 79930ed99a1fc284f41af5755908aa1fab948ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lepin?= Date: Sat, 11 May 2024 02:18:38 +0200 Subject: [PATCH 112/190] config: Add safe_z_home section for Creality CR-6 SE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Creality CR-6 SE has a strain gauge on its hotend used for z-probing and homing. Currently, running G28 to home all axes puts the hotend just outside of the print bed and thus assumes a wrong homing point for the Z axis. This change aims to address this issue by setting a safe Z-homing point (in the middle of the print bed) into the Creality CR-6 SE 2020 and 2021-revision config files. Signed-off-by: Stéphane Lepin --- config/printer-creality-cr6se-2020.cfg | 4 ++++ config/printer-creality-cr6se-2021.cfg | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/config/printer-creality-cr6se-2020.cfg b/config/printer-creality-cr6se-2020.cfg index 11d827eaf56f..2929a9ac4c30 100644 --- a/config/printer-creality-cr6se-2020.cfg +++ b/config/printer-creality-cr6se-2020.cfg @@ -98,6 +98,10 @@ z_offset: 0.0 speed: 2.0 samples: 5 +[safe_z_home] +home_xy_position: 117, 117 +z_hop: 10 + [filament_switch_sensor filament_sensor] pause_on_runout: true switch_pin: ^!PA7 diff --git a/config/printer-creality-cr6se-2021.cfg b/config/printer-creality-cr6se-2021.cfg index 12c17120f125..932a9263d20c 100644 --- a/config/printer-creality-cr6se-2021.cfg +++ b/config/printer-creality-cr6se-2021.cfg @@ -98,6 +98,10 @@ z_offset: 0.0 speed: 2.0 samples: 5 +[safe_z_home] +home_xy_position: 117, 117 +z_hop: 10 + [filament_switch_sensor filament_sensor] pause_on_runout: true switch_pin: ^!PA7 From 8f510da12bf51a58205ebd81873ec8efbaa32c43 Mon Sep 17 00:00:00 2001 From: Dropeffect GmbH Date: Thu, 2 May 2024 10:39:38 +0100 Subject: [PATCH 113/190] stm32g4: Fix ADC3 common interface registers name to ADC345_COMMON Use ADC345_COMMON instead of ADC3_COMMON for stm32g4 ADC3 channel. Signed-off-by: Amr Elsayed from Dropeffect GmbH --- src/stm32/stm32h7_adc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/stm32/stm32h7_adc.c b/src/stm32/stm32h7_adc.c index e9dc8f845473..3c217ca273c6 100644 --- a/src/stm32/stm32h7_adc.c +++ b/src/stm32/stm32h7_adc.c @@ -189,7 +189,11 @@ gpio_adc_setup(uint32_t pin) if (chan >= 2 * ADCIN_BANK_SIZE) { chan -= 2 * ADCIN_BANK_SIZE; adc = ADC3; +#if CONFIG_MACH_STM32G4 + adc_common = ADC345_COMMON; +#else adc_common = ADC3_COMMON; +#endif } else #endif #ifdef ADC2 From 80b55d352811c628bd0204fdda837acb9fce31d1 Mon Sep 17 00:00:00 2001 From: Dropeffect GmbH Date: Thu, 2 May 2024 11:25:08 +0100 Subject: [PATCH 114/190] stm32: Add FDCAN2 channel needed for stm32g4 alternate pins Some of the alternate pins defined are routed to FDCAN2 instead of FDCAN1, this commit uses the correct IRQ register and peripheral clock enable bit to enable FDCAN on those pins. Signed-off-by: Amr Elsayed from Dropeffect GmbH --- src/stm32/fdcan.c | 11 +++++++++-- src/stm32/stm32g4.c | 6 ++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/stm32/fdcan.c b/src/stm32/fdcan.c index b0e8c01d17fa..5344d26b195b 100644 --- a/src/stm32/fdcan.c +++ b/src/stm32/fdcan.c @@ -60,16 +60,23 @@ || CONFIG_STM32_CANBUS_PB5_PB6 ||CONFIG_STM32_CANBUS_PB12_PB13) #define SOC_CAN FDCAN1 #define MSG_RAM (((struct fdcan_ram_layout*)SRAMCAN_BASE)->fdcan1) + #if CONFIG_MACH_STM32H7 || CONFIG_MACH_STM32G4 + #define CAN_IT0_IRQn FDCAN1_IT0_IRQn + #endif #else #define SOC_CAN FDCAN2 #define MSG_RAM (((struct fdcan_ram_layout*)SRAMCAN_BASE)->fdcan2) + #if CONFIG_MACH_STM32H7 || CONFIG_MACH_STM32G4 + #define CAN_IT0_IRQn FDCAN2_IT0_IRQn + #endif #endif #if CONFIG_MACH_STM32G0 #define CAN_IT0_IRQn TIM16_FDCAN_IT0_IRQn #define CAN_FUNCTION GPIO_FUNCTION(3) // Alternative function mapping number -#elif CONFIG_MACH_STM32H7 || CONFIG_MACH_STM32G4 - #define CAN_IT0_IRQn FDCAN1_IT0_IRQn +#endif + +#if CONFIG_MACH_STM32H7 || CONFIG_MACH_STM32G4 #define CAN_FUNCTION GPIO_FUNCTION(9) // Alternative function mapping number #endif diff --git a/src/stm32/stm32g4.c b/src/stm32/stm32g4.c index 139ea8eaad64..1eed3ec188d6 100644 --- a/src/stm32/stm32g4.c +++ b/src/stm32/stm32g4.c @@ -22,6 +22,12 @@ lookup_clock_line(uint32_t periph_base) if (periph_base < APB2PERIPH_BASE) { uint32_t pos = (periph_base - APB1PERIPH_BASE) / 0x400; if (pos < 32) { +#if defined(FDCAN2_BASE) + if (periph_base == FDCAN2_BASE) + return (struct cline){.en = &RCC->APB1ENR1, + .rst = &RCC->APB1RSTR1, + .bit = 1 << 25}; +#endif return (struct cline){.en = &RCC->APB1ENR1, .rst = &RCC->APB1RSTR1, .bit = 1 << pos}; From 472d8e5b669de075d6d94b461b5f08d5901d3bda Mon Sep 17 00:00:00 2001 From: Dropeffect GmbH Date: Thu, 2 May 2024 11:42:30 +0100 Subject: [PATCH 115/190] stm32: Add STM32G474 chip to Kconfig Signed-off-by: Amr Elsayed from Dropeffect GmbH --- src/stm32/Kconfig | 7 +++++++ test/configs/stm32g474.config | 3 +++ 2 files changed, 10 insertions(+) create mode 100644 test/configs/stm32g474.config diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index d14622a25d41..dbd6ff959620 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -88,6 +88,9 @@ choice config MACH_STM32G431 bool "STM32G431" select MACH_STM32G4 + config MACH_STM32G474 + bool "STM32G474" + select MACH_STM32G4 config MACH_STM32H723 bool "STM32H723" select MACH_STM32H7 @@ -181,6 +184,7 @@ config MCU default "stm32g0b0xx" if MACH_STM32G0B0 default "stm32g0b1xx" if MACH_STM32G0B1 default "stm32g431xx" if MACH_STM32G431 + default "stm32g474xx" if MACH_STM32G474 default "stm32h723xx" if MACH_STM32H723 default "stm32h743xx" if MACH_STM32H743 default "stm32h750xx" if MACH_STM32H750 @@ -199,6 +203,7 @@ config CLOCK_FREQ default 216000000 if MACH_STM32F765 default 64000000 if MACH_STM32G0 default 150000000 if MACH_STM32G431 + default 170000000 if MACH_STM32G474 default 400000000 if MACH_STM32H7 # 400Mhz is max Klipper currently supports default 80000000 if MACH_STM32L412 default 64000000 if MACH_N32G45x && STM32_CLOCK_REF_INTERNAL @@ -213,6 +218,7 @@ config FLASH_SIZE default 0x40000 if MACH_STM32F2 || MACH_STM32F401 || MACH_STM32H723 default 0x80000 if MACH_STM32F4x5 || MACH_STM32F446 default 0x20000 if MACH_STM32G0 || MACH_STM32G431 + default 0x40000 if MACH_STM32G474 default 0x20000 if MACH_STM32H750 default 0x200000 if MACH_STM32H743 || MACH_STM32F765 default 0x20000 if MACH_N32G45x @@ -233,6 +239,7 @@ config RAM_SIZE default 0x2800 if MACH_STM32F103x6 default 0x5000 if MACH_STM32F103 && !MACH_STM32F103x6 # Ram size of stm32f103x8 default 0x8000 if MACH_STM32G431 + default 0x20000 if MACH_STM32G474 default 0xa000 if MACH_STM32L412 default 0x20000 if MACH_STM32F207 default 0x10000 if MACH_STM32F401 diff --git a/test/configs/stm32g474.config b/test/configs/stm32g474.config new file mode 100644 index 000000000000..da38a9179fb5 --- /dev/null +++ b/test/configs/stm32g474.config @@ -0,0 +1,3 @@ +# Base config file for STM32G474 ARM processor +CONFIG_MACH_STM32=y +CONFIG_MACH_STM32G474=y From f01c8853ca5c4f7d89ea1ae9a33779fe9c0446b1 Mon Sep 17 00:00:00 2001 From: Alex Voinea Date: Mon, 13 May 2024 22:31:39 +0200 Subject: [PATCH 116/190] tmc: Do not pass the frequency directly to the helpers Use the new get_tmc_frequency() instead. Signed-off-by: Alex Voinea --- klippy/extras/tmc.py | 5 +++-- klippy/extras/tmc2130.py | 2 +- klippy/extras/tmc2208.py | 2 +- klippy/extras/tmc2209.py | 2 +- klippy/extras/tmc2240.py | 2 +- klippy/extras/tmc5160.py | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/klippy/extras/tmc.py b/klippy/extras/tmc.py index 8143882abf19..4ce9466a4cbc 100644 --- a/klippy/extras/tmc.py +++ b/klippy/extras/tmc.py @@ -592,7 +592,7 @@ def TMCtstepHelper(step_dist, mres, tmc_freq, velocity): return 0xfffff # Helper to configure stealthChop-spreadCycle transition velocity -def TMCStealthchopHelper(config, mcu_tmc, tmc_freq): +def TMCStealthchopHelper(config, mcu_tmc): fields = mcu_tmc.get_fields() en_pwm_mode = False velocity = config.getfloat('stealthchop_threshold', None, minval=0.) @@ -606,7 +606,8 @@ def TMCStealthchopHelper(config, mcu_tmc, tmc_freq): rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig) step_dist = rotation_dist / steps_per_rotation mres = fields.get_field("mres") - tpwmthrs = TMCtstepHelper(step_dist, mres, tmc_freq, velocity) + tpwmthrs = TMCtstepHelper(step_dist, mres, mcu_tmc.get_tmc_frequency(), + velocity) fields.set_field("tpwmthrs", tpwmthrs) reg = fields.lookup_register("en_pwm_mode", None) diff --git a/klippy/extras/tmc2130.py b/klippy/extras/tmc2130.py index 62a9abbfe56b..3da346b4e874 100644 --- a/klippy/extras/tmc2130.py +++ b/klippy/extras/tmc2130.py @@ -296,7 +296,7 @@ def __init__(self, config): self.get_status = cmdhelper.get_status # Setup basic register values tmc.TMCWaveTableHelper(config, self.mcu_tmc) - tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY) + tmc.TMCStealthchopHelper(config, self.mcu_tmc) # Allow other registers to be set from the config set_config_field = self.fields.set_config_field # CHOPCONF diff --git a/klippy/extras/tmc2208.py b/klippy/extras/tmc2208.py index 421c53781613..86476acc57a2 100644 --- a/klippy/extras/tmc2208.py +++ b/klippy/extras/tmc2208.py @@ -197,7 +197,7 @@ def __init__(self, config): self.get_status = cmdhelper.get_status # Setup basic register values self.fields.set_field("mstep_reg_select", True) - tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY) + tmc.TMCStealthchopHelper(config, self.mcu_tmc) # Allow other registers to be set from the config set_config_field = self.fields.set_config_field # GCONF diff --git a/klippy/extras/tmc2209.py b/klippy/extras/tmc2209.py index c248c2d68c6f..1149cdc211ab 100644 --- a/klippy/extras/tmc2209.py +++ b/klippy/extras/tmc2209.py @@ -73,7 +73,7 @@ def __init__(self, config): self.get_status = cmdhelper.get_status # Setup basic register values self.fields.set_field("mstep_reg_select", True) - tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY) + tmc.TMCStealthchopHelper(config, self.mcu_tmc) # Allow other registers to be set from the config set_config_field = self.fields.set_config_field # GCONF diff --git a/klippy/extras/tmc2240.py b/klippy/extras/tmc2240.py index 14d5dd9188be..f4c75aa99a0d 100644 --- a/klippy/extras/tmc2240.py +++ b/klippy/extras/tmc2240.py @@ -364,7 +364,7 @@ def __init__(self, config): # Setup basic register values tmc.TMCWaveTableHelper(config, self.mcu_tmc) self.fields.set_config_field(config, "offset_sin90", 0) - tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY) + tmc.TMCStealthchopHelper(config, self.mcu_tmc) set_config_field = self.fields.set_config_field # GCONF set_config_field(config, "multistep_filt", True) diff --git a/klippy/extras/tmc5160.py b/klippy/extras/tmc5160.py index 7ff47abf9f88..e37435e6bf73 100644 --- a/klippy/extras/tmc5160.py +++ b/klippy/extras/tmc5160.py @@ -335,7 +335,7 @@ def __init__(self, config): self.get_status = cmdhelper.get_status # Setup basic register values tmc.TMCWaveTableHelper(config, self.mcu_tmc) - tmc.TMCStealthchopHelper(config, self.mcu_tmc, TMC_FREQUENCY) + tmc.TMCStealthchopHelper(config, self.mcu_tmc) set_config_field = self.fields.set_config_field # GCONF set_config_field(config, "multistep_filt", True) From 1ca1054957bef8acc76c0e23955dcca54bf17bc8 Mon Sep 17 00:00:00 2001 From: Alex Voinea Date: Wed, 22 Mar 2023 19:36:35 +0100 Subject: [PATCH 117/190] tmc2130: implement missing HighVelocity fields in the config Signed-off-by: Alex Voinea --- docs/Config_Reference.md | 2 ++ klippy/extras/tmc2130.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index b66140552938..7516122fad9a 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -3347,6 +3347,8 @@ run_current: #driver_TOFF: 4 #driver_HEND: 7 #driver_HSTRT: 0 +#driver_VHIGHFS: 0 +#driver_VHIGHCHM: 0 #driver_PWM_AUTOSCALE: True #driver_PWM_FREQ: 1 #driver_PWM_GRAD: 4 diff --git a/klippy/extras/tmc2130.py b/klippy/extras/tmc2130.py index 3da346b4e874..98cfea53eaca 100644 --- a/klippy/extras/tmc2130.py +++ b/klippy/extras/tmc2130.py @@ -304,6 +304,8 @@ def __init__(self, config): set_config_field(config, "hstrt", 0) set_config_field(config, "hend", 7) set_config_field(config, "tbl", 1) + set_config_field(config, "vhighfs", 0) + set_config_field(config, "vhighchm", 0) # COOLCONF set_config_field(config, "sgt", 0) # IHOLDIRUN From 0f3f29101ca49e913354811430d43776f13bcdbb Mon Sep 17 00:00:00 2001 From: Alex Voinea Date: Thu, 23 Mar 2023 21:09:53 +0100 Subject: [PATCH 118/190] tmc: Implement CoolStep fields for all drivers Signed-off-by: Alex Voinea --- docs/Config_Reference.md | 11 +++++++++++ klippy/extras/tmc2130.py | 6 ++++++ klippy/extras/tmc2209.py | 6 ++++++ 3 files changed, 23 insertions(+) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 7516122fad9a..ee09bf2c000b 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -3354,6 +3354,12 @@ run_current: #driver_PWM_GRAD: 4 #driver_PWM_AMPL: 128 #driver_SGT: 0 +#driver_SEMIN: 0 +#driver_SEUP: 0 +#driver_SEMAX: 0 +#driver_SEDN: 0 +#driver_SEIMIN: 0 +#driver_SFILT: 0 # Set the given register during the configuration of the TMC2130 # chip. This may be used to set custom motor parameters. The # defaults for each parameter are next to the parameter name in the @@ -3469,6 +3475,11 @@ run_current: #driver_PWM_GRAD: 14 #driver_PWM_OFS: 36 #driver_SGTHRS: 0 +#driver_SEMIN: 0 +#driver_SEUP: 0 +#driver_SEMAX: 0 +#driver_SEDN: 0 +#driver_SEIMIN: 0 # Set the given register during the configuration of the TMC2209 # 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/tmc2130.py b/klippy/extras/tmc2130.py index 98cfea53eaca..f995fb7ab566 100644 --- a/klippy/extras/tmc2130.py +++ b/klippy/extras/tmc2130.py @@ -307,7 +307,13 @@ def __init__(self, config): set_config_field(config, "vhighfs", 0) set_config_field(config, "vhighchm", 0) # COOLCONF + set_config_field(config, "semin", 0) + set_config_field(config, "seup", 0) + set_config_field(config, "semax", 0) + set_config_field(config, "sedn", 0) + set_config_field(config, "seimin", 0) set_config_field(config, "sgt", 0) + set_config_field(config, "sfilt", 0) # IHOLDIRUN set_config_field(config, "iholddelay", 8) # PWMCONF diff --git a/klippy/extras/tmc2209.py b/klippy/extras/tmc2209.py index 1149cdc211ab..ac997a81c37b 100644 --- a/klippy/extras/tmc2209.py +++ b/klippy/extras/tmc2209.py @@ -83,6 +83,12 @@ def __init__(self, config): set_config_field(config, "hstrt", 5) set_config_field(config, "hend", 0) set_config_field(config, "tbl", 2) + # COOLCONF + set_config_field(config, "semin", 0) + set_config_field(config, "seup", 0) + set_config_field(config, "semax", 0) + set_config_field(config, "sedn", 0) + set_config_field(config, "seimin", 0) # IHOLDIRUN set_config_field(config, "iholddelay", 8) # PWMCONF From 5249d955bb0a4ee131cc3def5d9d53d61e2d4334 Mon Sep 17 00:00:00 2001 From: Alex Voinea Date: Tue, 14 May 2024 22:21:58 +0200 Subject: [PATCH 119/190] tmc: Implement coolstep_threshold for drivers that support it Signed-off-by: Alex Voinea --- docs/Config_Reference.md | 28 ++++++++++++++++++++++++++++ klippy/extras/tmc.py | 16 ++++++++++++++++ klippy/extras/tmc2130.py | 1 + klippy/extras/tmc2209.py | 1 + klippy/extras/tmc2240.py | 2 ++ klippy/extras/tmc5160.py | 2 ++ 6 files changed, 50 insertions(+) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index ee09bf2c000b..d48715d87e62 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -3317,6 +3317,13 @@ run_current: # set, "stealthChop" mode will be enabled if the stepper motor # velocity is below this value. The default is 0, which disables # "stealthChop" mode. +#coolstep_threshold: +# The velocity (in mm/s) to set the TMC driver internal "CoolStep" +# threshold to. When set, the coolstep feature will be enabled if +# the stepper motor velocity is near or above this value. Important +# - if coolstep_threshold is set and "sensorless homing" is used, +# then one must ensure that the homing speed is above the coolstep +# threshold! The default is to not enable the coolstep feature. #driver_MSLUT0: 2863314260 #driver_MSLUT1: 1251300522 #driver_MSLUT2: 608774441 @@ -3456,6 +3463,13 @@ run_current: #sense_resistor: 0.110 #stealthchop_threshold: 0 # See the "tmc2208" section for the definition of these parameters. +#coolstep_threshold: +# The velocity (in mm/s) to set the TMC driver internal "CoolStep" +# threshold to. When set, the coolstep feature will be enabled if +# the stepper motor velocity is near or above this value. Important +# - if coolstep_threshold is set and "sensorless homing" is used, +# then one must ensure that the homing speed is above the coolstep +# threshold! The default is to not enable the coolstep feature. #uart_address: # The address of the TMC2209 chip for UART messages (an integer # between 0 and 3). This is typically used when multiple TMC2209 @@ -3614,6 +3628,13 @@ run_current: # set, "stealthChop" mode will be enabled if the stepper motor # velocity is below this value. The default is 0, which disables # "stealthChop" mode. +#coolstep_threshold: +# The velocity (in mm/s) to set the TMC driver internal "CoolStep" +# threshold to. When set, the coolstep feature will be enabled if +# the stepper motor velocity is near or above this value. Important +# - if coolstep_threshold is set and "sensorless homing" is used, +# then one must ensure that the homing speed is above the coolstep +# threshold! The default is to not enable the coolstep feature. #driver_MSLUT0: 2863314260 #driver_MSLUT1: 1251300522 #driver_MSLUT2: 608774441 @@ -3735,6 +3756,13 @@ run_current: # set, "stealthChop" mode will be enabled if the stepper motor # velocity is below this value. The default is 0, which disables # "stealthChop" mode. +#coolstep_threshold: +# The velocity (in mm/s) to set the TMC driver internal "CoolStep" +# threshold to. When set, the coolstep feature will be enabled if +# the stepper motor velocity is near or above this value. Important +# - if coolstep_threshold is set and "sensorless homing" is used, +# then one must ensure that the homing speed is above the coolstep +# threshold! The default is to not enable the coolstep feature. #driver_MSLUT0: 2863314260 #driver_MSLUT1: 1251300522 #driver_MSLUT2: 608774441 diff --git a/klippy/extras/tmc.py b/klippy/extras/tmc.py index 4ce9466a4cbc..e7c0239f0b97 100644 --- a/klippy/extras/tmc.py +++ b/klippy/extras/tmc.py @@ -616,3 +616,19 @@ def TMCStealthchopHelper(config, mcu_tmc): else: # TMC2208 uses en_spreadCycle fields.set_field("en_spreadcycle", not en_pwm_mode) + +# Helper to configure StallGuard and CoolStep minimum velocity +def TMCVcoolthrsHelper(config, mcu_tmc): + fields = mcu_tmc.get_fields() + velocity = config.getfloat('coolstep_threshold', None, minval=0.) + tcoolthrs = 0 + + if velocity is not None: + stepper_name = " ".join(config.get_name().split()[1:]) + sconfig = config.getsection(stepper_name) + rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig) + step_dist = rotation_dist / steps_per_rotation + mres = fields.get_field("mres") + tcoolthrs = TMCtstepHelper(step_dist, mres, + mcu_tmc.get_tmc_frequency(), velocity) + fields.set_field("tcoolthrs", tcoolthrs) diff --git a/klippy/extras/tmc2130.py b/klippy/extras/tmc2130.py index f995fb7ab566..44d4342ba8b3 100644 --- a/klippy/extras/tmc2130.py +++ b/klippy/extras/tmc2130.py @@ -297,6 +297,7 @@ def __init__(self, config): # Setup basic register values tmc.TMCWaveTableHelper(config, self.mcu_tmc) tmc.TMCStealthchopHelper(config, self.mcu_tmc) + tmc.TMCVcoolthrsHelper(config, self.mcu_tmc) # Allow other registers to be set from the config set_config_field = self.fields.set_config_field # CHOPCONF diff --git a/klippy/extras/tmc2209.py b/klippy/extras/tmc2209.py index ac997a81c37b..fbd8d1c101e0 100644 --- a/klippy/extras/tmc2209.py +++ b/klippy/extras/tmc2209.py @@ -74,6 +74,7 @@ def __init__(self, config): # Setup basic register values self.fields.set_field("mstep_reg_select", True) tmc.TMCStealthchopHelper(config, self.mcu_tmc) + tmc.TMCVcoolthrsHelper(config, self.mcu_tmc) # Allow other registers to be set from the config set_config_field = self.fields.set_config_field # GCONF diff --git a/klippy/extras/tmc2240.py b/klippy/extras/tmc2240.py index f4c75aa99a0d..3989b9021bd2 100644 --- a/klippy/extras/tmc2240.py +++ b/klippy/extras/tmc2240.py @@ -365,6 +365,8 @@ def __init__(self, config): tmc.TMCWaveTableHelper(config, self.mcu_tmc) self.fields.set_config_field(config, "offset_sin90", 0) tmc.TMCStealthchopHelper(config, self.mcu_tmc) + tmc.TMCVcoolthrsHelper(config, self.mcu_tmc) + # Allow other registers to be set from the config set_config_field = self.fields.set_config_field # GCONF set_config_field(config, "multistep_filt", True) diff --git a/klippy/extras/tmc5160.py b/klippy/extras/tmc5160.py index e37435e6bf73..97d2d6f94b9a 100644 --- a/klippy/extras/tmc5160.py +++ b/klippy/extras/tmc5160.py @@ -336,6 +336,8 @@ def __init__(self, config): # Setup basic register values tmc.TMCWaveTableHelper(config, self.mcu_tmc) tmc.TMCStealthchopHelper(config, self.mcu_tmc) + tmc.TMCVcoolthrsHelper(config, self.mcu_tmc) + # Allow other registers to be set from the config set_config_field = self.fields.set_config_field # GCONF set_config_field(config, "multistep_filt", True) From ed8dca8df08924e7df9291d5e4ff88c9fee4ca4e Mon Sep 17 00:00:00 2001 From: Alex Voinea Date: Tue, 14 May 2024 22:23:06 +0200 Subject: [PATCH 120/190] tmc: Implement high_velocity_threshold for drivers that support it Signed-off-by: Alex Voinea --- docs/Config_Reference.md | 24 ++++++++++++++++++++++++ klippy/extras/tmc.py | 17 +++++++++++++++++ klippy/extras/tmc2130.py | 1 + klippy/extras/tmc2240.py | 1 + klippy/extras/tmc5160.py | 4 ++++ 5 files changed, 47 insertions(+) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index d48715d87e62..9a26556f0e42 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -3324,6 +3324,14 @@ run_current: # - if coolstep_threshold is set and "sensorless homing" is used, # then one must ensure that the homing speed is above the coolstep # threshold! The default is to not enable the coolstep feature. +#high_velocity_threshold: +# The velocity (in mm/s) to set the TMC driver internal "high +# velocity" threshold (THIGH) to. This is typically used to disable +# the "CoolStep" feature at high speeds. Important - if +# high_velocity_threshold is set and "sensorless homing" is used, +# then one must ensure that the homing speed is below the high +# velocity threshold! The default is to not set a TMC "high +# velocity" threshold. #driver_MSLUT0: 2863314260 #driver_MSLUT1: 1251300522 #driver_MSLUT2: 608774441 @@ -3635,6 +3643,14 @@ run_current: # - if coolstep_threshold is set and "sensorless homing" is used, # then one must ensure that the homing speed is above the coolstep # threshold! The default is to not enable the coolstep feature. +#high_velocity_threshold: +# The velocity (in mm/s) to set the TMC driver internal "high +# velocity" threshold (THIGH) to. This is typically used to disable +# the "CoolStep" feature at high speeds. Important - if +# high_velocity_threshold is set and "sensorless homing" is used, +# then one must ensure that the homing speed is below the high +# velocity threshold! The default is to not set a TMC "high +# velocity" threshold. #driver_MSLUT0: 2863314260 #driver_MSLUT1: 1251300522 #driver_MSLUT2: 608774441 @@ -3763,6 +3779,14 @@ run_current: # - if coolstep_threshold is set and "sensorless homing" is used, # then one must ensure that the homing speed is above the coolstep # threshold! The default is to not enable the coolstep feature. +#high_velocity_threshold: +# The velocity (in mm/s) to set the TMC driver internal "high +# velocity" threshold (THIGH) to. This is typically used to disable +# the "CoolStep" feature at high speeds. Important - if +# high_velocity_threshold is set and "sensorless homing" is used, +# then one must ensure that the homing speed is below the high +# velocity threshold! The default is to not set a TMC "high +# velocity" threshold. #driver_MSLUT0: 2863314260 #driver_MSLUT1: 1251300522 #driver_MSLUT2: 608774441 diff --git a/klippy/extras/tmc.py b/klippy/extras/tmc.py index e7c0239f0b97..366e00b15c01 100644 --- a/klippy/extras/tmc.py +++ b/klippy/extras/tmc.py @@ -632,3 +632,20 @@ def TMCVcoolthrsHelper(config, mcu_tmc): tcoolthrs = TMCtstepHelper(step_dist, mres, mcu_tmc.get_tmc_frequency(), velocity) fields.set_field("tcoolthrs", tcoolthrs) + +# Helper to configure StallGuard and CoolStep maximum velocity and +# SpreadCycle-FullStepping (High velocity) mode threshold. +def TMCVhighHelper(config, mcu_tmc): + fields = mcu_tmc.get_fields() + velocity = config.getfloat('high_velocity_threshold', None, minval=0.) + thigh = 0 + + if velocity is not None: + stepper_name = " ".join(config.get_name().split()[1:]) + sconfig = config.getsection(stepper_name) + rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig) + step_dist = rotation_dist / steps_per_rotation + mres = fields.get_field("mres") + thigh = TMCtstepHelper(step_dist, mres, + mcu_tmc.get_tmc_frequency(), velocity) + fields.set_field("thigh", thigh) diff --git a/klippy/extras/tmc2130.py b/klippy/extras/tmc2130.py index 44d4342ba8b3..20a25c66c514 100644 --- a/klippy/extras/tmc2130.py +++ b/klippy/extras/tmc2130.py @@ -298,6 +298,7 @@ def __init__(self, config): tmc.TMCWaveTableHelper(config, self.mcu_tmc) tmc.TMCStealthchopHelper(config, self.mcu_tmc) tmc.TMCVcoolthrsHelper(config, self.mcu_tmc) + tmc.TMCVhighHelper(config, self.mcu_tmc) # Allow other registers to be set from the config set_config_field = self.fields.set_config_field # CHOPCONF diff --git a/klippy/extras/tmc2240.py b/klippy/extras/tmc2240.py index 3989b9021bd2..1e2a49157f36 100644 --- a/klippy/extras/tmc2240.py +++ b/klippy/extras/tmc2240.py @@ -366,6 +366,7 @@ def __init__(self, config): self.fields.set_config_field(config, "offset_sin90", 0) tmc.TMCStealthchopHelper(config, self.mcu_tmc) tmc.TMCVcoolthrsHelper(config, self.mcu_tmc) + tmc.TMCVhighHelper(config, self.mcu_tmc) # Allow other registers to be set from the config set_config_field = self.fields.set_config_field # GCONF diff --git a/klippy/extras/tmc5160.py b/klippy/extras/tmc5160.py index 97d2d6f94b9a..02e16cd4d003 100644 --- a/klippy/extras/tmc5160.py +++ b/klippy/extras/tmc5160.py @@ -242,6 +242,9 @@ Fields["TSTEP"] = { "tstep": 0xfffff << 0 } +Fields["THIGH"] = { + "thigh": 0xfffff << 0 +} SignedFields = ["cur_a", "cur_b", "sgt", "xactual", "vactual", "pwm_scale_auto"] @@ -337,6 +340,7 @@ def __init__(self, config): tmc.TMCWaveTableHelper(config, self.mcu_tmc) tmc.TMCStealthchopHelper(config, self.mcu_tmc) tmc.TMCVcoolthrsHelper(config, self.mcu_tmc) + tmc.TMCVhighHelper(config, self.mcu_tmc) # Allow other registers to be set from the config set_config_field = self.fields.set_config_field # GCONF From e0cbd7b5fc1ce6d1dfbc8daf8e59f57bf3c5e5b9 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 14 May 2024 18:22:58 -0400 Subject: [PATCH 121/190] docs: Minor wording change to coolstep_threshold in Config_Reference.md Signed-off-by: Kevin O'Connor --- docs/Config_Reference.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 9a26556f0e42..9d5cb763ca17 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -3319,7 +3319,7 @@ run_current: # "stealthChop" mode. #coolstep_threshold: # The velocity (in mm/s) to set the TMC driver internal "CoolStep" -# threshold to. When set, the coolstep feature will be enabled if +# threshold to. If set, the coolstep feature will be enabled when # the stepper motor velocity is near or above this value. Important # - if coolstep_threshold is set and "sensorless homing" is used, # then one must ensure that the homing speed is above the coolstep @@ -3473,7 +3473,7 @@ run_current: # See the "tmc2208" section for the definition of these parameters. #coolstep_threshold: # The velocity (in mm/s) to set the TMC driver internal "CoolStep" -# threshold to. When set, the coolstep feature will be enabled if +# threshold to. If set, the coolstep feature will be enabled when # the stepper motor velocity is near or above this value. Important # - if coolstep_threshold is set and "sensorless homing" is used, # then one must ensure that the homing speed is above the coolstep @@ -3638,7 +3638,7 @@ run_current: # "stealthChop" mode. #coolstep_threshold: # The velocity (in mm/s) to set the TMC driver internal "CoolStep" -# threshold to. When set, the coolstep feature will be enabled if +# threshold to. If set, the coolstep feature will be enabled when # the stepper motor velocity is near or above this value. Important # - if coolstep_threshold is set and "sensorless homing" is used, # then one must ensure that the homing speed is above the coolstep @@ -3774,7 +3774,7 @@ run_current: # "stealthChop" mode. #coolstep_threshold: # The velocity (in mm/s) to set the TMC driver internal "CoolStep" -# threshold to. When set, the coolstep feature will be enabled if +# threshold to. If set, the coolstep feature will be enabled when # the stepper motor velocity is near or above this value. Important # - if coolstep_threshold is set and "sensorless homing" is used, # then one must ensure that the homing speed is above the coolstep From dae8b8cacff583b180c650d9a6d1601907a12f1e Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 15 May 2024 11:22:22 -0400 Subject: [PATCH 122/190] docs: Update jinja2 requirement in mkdocs-requirements.txt Update the jinja2 version to suppress security warnings. Klipper is not impacted by the vulnerability, but it is harmless to update the version. 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 7392889594fd..96bf60051896 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.3 +jinja2==3.1.4 mkdocs==1.2.4 mkdocs-material==8.1.3 mkdocs-simple-hooks==0.1.3 From 694d38c79105f2d33242487faa0532d1291ee7b6 Mon Sep 17 00:00:00 2001 From: voidtrance <30448940+voidtrance@users.noreply.github.com> Date: Wed, 15 May 2024 18:38:42 -0700 Subject: [PATCH 123/190] bed_mesh: Fix adaptive probe count on delta printers (#6600) Round beds require an odd number of probe points in order to prevent erroneously truncating the mesh. The adaptive mesh algorithm did not consider that and as a result, it was possible to generate adaptive meshes with even number of probe points. This change fixes this by increasing the probe point count by 1 in cases where the adaptive probe points are even. Signed-off-by: Mitko Haralanov --- klippy/extras/bed_mesh.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index 87f2324a4376..095ccf1fdc8c 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -652,8 +652,11 @@ def set_adaptive_mesh(self, gcmd): self.origin = adapted_origin self.mesh_min = (-self.radius, -self.radius) self.mesh_max = (self.radius, self.radius) + new_probe_count = max(new_x_probe_count, new_y_probe_count) + # Adaptive meshes require odd number of points + new_probe_count += 1 - (new_probe_count % 2) self.mesh_config["x_count"] = self.mesh_config["y_count"] = \ - max(new_x_probe_count, new_y_probe_count) + new_probe_count else: self.mesh_min = adjusted_mesh_min self.mesh_max = adjusted_mesh_max From b7f7b8a346388cc32d80b6e6f60e5fdb4cbd3ce6 Mon Sep 17 00:00:00 2001 From: Frans-willem Hardijzer Date: Wed, 7 Feb 2024 20:25:36 +0100 Subject: [PATCH 124/190] idex_modes: Bugfix for kinematic position calculation. idex_mode would swap the X and dual-carriage rail in some cases (homing), but not in others. As such, the position calculation was correct while homing, but incorrect for the second carriage during normal moves. This commit fixes homing to work without swapped rails, removes the swapping of rails while homing, and removes the ability to swap rails (as it is now no longer used). Fix has been tested in a Hybrid_CoreXY IDEX printer (Voron Double Dragon). Hybrid_CoreXZ has identical changes and is similar enough that I am confident it will work as intended. Changes to cartesion seem simple enough, but would benefit from someone running a couple of tests. Signed-off-by: Frans-Willem Hardijzer --- klippy/kinematics/cartesian.py | 17 ++++++++++++----- klippy/kinematics/hybrid_corexy.py | 12 +++++++----- klippy/kinematics/hybrid_corexz.py | 12 +++++++----- klippy/kinematics/idex_modes.py | 15 ++++++++------- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index 6d576b5b5a54..9774672e25cc 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -52,20 +52,27 @@ def __init__(self, toolhead, config): def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] def calc_position(self, stepper_positions): - return [stepper_positions[rail.get_name()] for rail in self.rails] + rails = self.rails + if self.dc_module: + primary_rail = self.dc_module.get_primary_rail().get_rail() + rails = (rails[:self.dc_module.axis] + + [primary_rail] + rails[self.dc_module.axis+1:]) + return [stepper_positions[rail.get_name()] for rail in rails] def update_limits(self, i, range): l, h = self.limits[i] # Only update limits if this axis was already homed, # otherwise leave in un-homed state. if l <= h: self.limits[i] = range - def override_rail(self, i, rail): - self.rails[i] = rail def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) - if i in homing_axes: - self.limits[i] = rail.get_range() + for axis in homing_axes: + if self.dc_module and axis == self.dc_module.axis: + rail = self.dc_module.get_primary_rail().get_rail() + 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) diff --git a/klippy/kinematics/hybrid_corexy.py b/klippy/kinematics/hybrid_corexy.py index 1c2164eb745a..e852826afba6 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -57,7 +57,7 @@ def calc_position(self, stepper_positions): pos = [stepper_positions[rail.get_name()] for rail in self.rails] if (self.dc_module is not None and 'PRIMARY' == \ self.dc_module.get_status()['carriage_1']): - return [pos[0] - pos[1], pos[1], pos[2]] + return [pos[3] - pos[1], pos[1], pos[2]] else: return [pos[0] + pos[1], pos[1], pos[2]] def update_limits(self, i, range): @@ -66,13 +66,15 @@ def update_limits(self, i, range): # otherwise leave in un-homed state. if l <= h: self.limits[i] = range - def override_rail(self, i, rail): - self.rails[i] = rail def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) - if i in homing_axes: - self.limits[i] = rail.get_range() + for axis in homing_axes: + if self.dc_module and axis == self.dc_module.axis: + rail = self.dc_module.get_primary_rail().get_rail() + 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) diff --git a/klippy/kinematics/hybrid_corexz.py b/klippy/kinematics/hybrid_corexz.py index 0eaea117ed2a..58e6b0d390c9 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -57,7 +57,7 @@ def calc_position(self, stepper_positions): pos = [stepper_positions[rail.get_name()] for rail in self.rails] if (self.dc_module is not None and 'PRIMARY' == \ self.dc_module.get_status()['carriage_1']): - return [pos[0] - pos[2], pos[1], pos[2]] + return [pos[3] - pos[2], pos[1], pos[2]] else: return [pos[0] + pos[2], pos[1], pos[2]] def update_limits(self, i, range): @@ -66,13 +66,15 @@ def update_limits(self, i, range): # otherwise leave in un-homed state. if l <= h: self.limits[i] = range - def override_rail(self, i, rail): - self.rails[i] = rail def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) - if i in homing_axes: - self.limits[i] = rail.get_range() + for axis in homing_axes: + if self.dc_module and axis == self.dc_module.axis: + rail = self.dc_module.get_primary_rail().get_rail() + 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) diff --git a/klippy/kinematics/idex_modes.py b/klippy/kinematics/idex_modes.py index 2ce91afe85a5..f2618d0805a7 100644 --- a/klippy/kinematics/idex_modes.py +++ b/klippy/kinematics/idex_modes.py @@ -42,7 +42,12 @@ def __init__(self, dc_config, rail_0, rail_1, axis): desc=self.cmd_RESTORE_DUAL_CARRIAGE_STATE_help) def get_rails(self): return self.dc - def toggle_active_dc_rail(self, index, override_rail=False): + def get_primary_rail(self): + for rail in self.dc: + if rail.mode == PRIMARY: + return rail + return None + def toggle_active_dc_rail(self, index): toolhead = self.printer.lookup_object('toolhead') toolhead.flush_step_generation() pos = toolhead.get_position() @@ -52,15 +57,11 @@ def toggle_active_dc_rail(self, index, override_rail=False): if i != index: if dc.is_active(): dc.inactivate(pos) - if override_rail: - kin.override_rail(3, dc_rail) target_dc = self.dc[index] if target_dc.mode != PRIMARY: newpos = pos[:self.axis] + [target_dc.get_axis_position(pos)] \ + pos[self.axis+1:] target_dc.activate(PRIMARY, newpos, old_position=pos) - if override_rail: - kin.override_rail(self.axis, target_dc.get_rail()) toolhead.set_position(newpos) kin.update_limits(self.axis, target_dc.get_rail().get_range()) def home(self, homing_state): @@ -72,10 +73,10 @@ def home(self, homing_state): # the same direction and the first carriage homes on the second one enumerated_dcs.reverse() for i, dc_rail in enumerated_dcs: - self.toggle_active_dc_rail(i, override_rail=True) + self.toggle_active_dc_rail(i) kin.home_axis(homing_state, self.axis, dc_rail.get_rail()) # Restore the original rails ordering - self.toggle_active_dc_rail(0, override_rail=True) + self.toggle_active_dc_rail(0) def get_status(self, eventtime=None): return {('carriage_%d' % (i,)) : dc.mode for (i, dc) in enumerate(self.dc)} From faee2c0e52506f157d38b747613df213d659226a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 14 May 2024 18:37:38 -0400 Subject: [PATCH 125/190] tmc: Refactor TMCtstepHelper() Update TMCtstepHelper() to obtain the step_distance, tmc_frequency, and mres fields directly. Signed-off-by: Kevin O'Connor --- klippy/extras/tmc.py | 59 +++++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/klippy/extras/tmc.py b/klippy/extras/tmc.py index 366e00b15c01..1a5059fa83fb 100644 --- a/klippy/extras/tmc.py +++ b/klippy/extras/tmc.py @@ -278,16 +278,14 @@ def cmd_SET_TMC_FIELD(self, gcmd): raise gcmd.error("Unknown field name '%s'" % (field_name,)) value = gcmd.get_int('VALUE', None) velocity = gcmd.get_float('VELOCITY', None, minval=0.) - tmc_frequency = self.mcu_tmc.get_tmc_frequency() - if tmc_frequency is None and velocity is not None: - raise gcmd.error("VELOCITY parameter not supported by this driver") if (value is None) == (velocity is None): raise gcmd.error("Specify either VALUE or VELOCITY") if velocity is not None: - step_dist = self.stepper.get_step_dist() - mres = self.fields.get_field("mres") - value = TMCtstepHelper(step_dist, mres, tmc_frequency, - velocity) + if self.mcu_tmc.get_tmc_frequency() is None: + raise gcmd.error( + "VELOCITY parameter not supported by this driver") + value = TMCtstepHelper(self.mcu_tmc, velocity, + pstepper=self.stepper) reg_val = self.fields.set_field(field_name, value) print_time = self.printer.lookup_object('toolhead').get_last_move_time() self.mcu_tmc.set_register(reg_name, reg_val, print_time) @@ -583,13 +581,21 @@ def TMCMicrostepHelper(config, mcu_tmc): fields.set_field("intpol", config.getboolean("interpolate", True)) # Helper for calculating TSTEP based values from velocity -def TMCtstepHelper(step_dist, mres, tmc_freq, velocity): - if velocity > 0.: - step_dist_256 = step_dist / (1 << mres) - threshold = int(tmc_freq * step_dist_256 / velocity + .5) - return max(0, min(0xfffff, threshold)) - else: +def TMCtstepHelper(mcu_tmc, velocity, pstepper=None, config=None): + if velocity <= 0.: return 0xfffff + if pstepper is not None: + step_dist = pstepper.get_step_dist() + else: + stepper_name = " ".join(config.get_name().split()[1:]) + sconfig = config.getsection(stepper_name) + rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig) + step_dist = rotation_dist / steps_per_rotation + mres = mcu_tmc.get_fields().get_field("mres") + step_dist_256 = step_dist / (1 << mres) + tmc_freq = mcu_tmc.get_tmc_frequency() + threshold = int(tmc_freq * step_dist_256 / velocity + .5) + return max(0, min(0xfffff, threshold)) # Helper to configure stealthChop-spreadCycle transition velocity def TMCStealthchopHelper(config, mcu_tmc): @@ -600,14 +606,7 @@ def TMCStealthchopHelper(config, mcu_tmc): if velocity is not None: en_pwm_mode = True - - stepper_name = " ".join(config.get_name().split()[1:]) - sconfig = config.getsection(stepper_name) - rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig) - step_dist = rotation_dist / steps_per_rotation - mres = fields.get_field("mres") - tpwmthrs = TMCtstepHelper(step_dist, mres, mcu_tmc.get_tmc_frequency(), - velocity) + tpwmthrs = TMCtstepHelper(mcu_tmc, velocity, config=config) fields.set_field("tpwmthrs", tpwmthrs) reg = fields.lookup_register("en_pwm_mode", None) @@ -622,15 +621,8 @@ def TMCVcoolthrsHelper(config, mcu_tmc): fields = mcu_tmc.get_fields() velocity = config.getfloat('coolstep_threshold', None, minval=0.) tcoolthrs = 0 - if velocity is not None: - stepper_name = " ".join(config.get_name().split()[1:]) - sconfig = config.getsection(stepper_name) - rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig) - step_dist = rotation_dist / steps_per_rotation - mres = fields.get_field("mres") - tcoolthrs = TMCtstepHelper(step_dist, mres, - mcu_tmc.get_tmc_frequency(), velocity) + tcoolthrs = TMCtstepHelper(mcu_tmc, velocity, config=config) fields.set_field("tcoolthrs", tcoolthrs) # Helper to configure StallGuard and CoolStep maximum velocity and @@ -639,13 +631,6 @@ def TMCVhighHelper(config, mcu_tmc): fields = mcu_tmc.get_fields() velocity = config.getfloat('high_velocity_threshold', None, minval=0.) thigh = 0 - if velocity is not None: - stepper_name = " ".join(config.get_name().split()[1:]) - sconfig = config.getsection(stepper_name) - rotation_dist, steps_per_rotation = stepper.parse_step_distance(sconfig) - step_dist = rotation_dist / steps_per_rotation - mres = fields.get_field("mres") - thigh = TMCtstepHelper(step_dist, mres, - mcu_tmc.get_tmc_frequency(), velocity) + thigh = TMCtstepHelper(mcu_tmc, velocity, config=config) fields.set_field("thigh", thigh) From 2efde0111e949c87f72409fbe43a6a96d5aa07f5 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 14 May 2024 21:27:01 -0400 Subject: [PATCH 126/190] tmc: Save and restore thigh during sensorless homing Make sure thigh is set to zero during sensorless homing, as it would not make sense for it to be enabled. Signed-off-by: Kevin O'Connor --- docs/Config_Reference.md | 21 ++++++--------------- klippy/extras/tmc.py | 19 +++++++++++++++++-- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 9d5cb763ca17..1338d46f989c 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -3327,11 +3327,8 @@ run_current: #high_velocity_threshold: # The velocity (in mm/s) to set the TMC driver internal "high # velocity" threshold (THIGH) to. This is typically used to disable -# the "CoolStep" feature at high speeds. Important - if -# high_velocity_threshold is set and "sensorless homing" is used, -# then one must ensure that the homing speed is below the high -# velocity threshold! The default is to not set a TMC "high -# velocity" threshold. +# the "CoolStep" feature at high speeds. The default is to not set a +# TMC "high velocity" threshold. #driver_MSLUT0: 2863314260 #driver_MSLUT1: 1251300522 #driver_MSLUT2: 608774441 @@ -3646,11 +3643,8 @@ run_current: #high_velocity_threshold: # The velocity (in mm/s) to set the TMC driver internal "high # velocity" threshold (THIGH) to. This is typically used to disable -# the "CoolStep" feature at high speeds. Important - if -# high_velocity_threshold is set and "sensorless homing" is used, -# then one must ensure that the homing speed is below the high -# velocity threshold! The default is to not set a TMC "high -# velocity" threshold. +# the "CoolStep" feature at high speeds. The default is to not set a +# TMC "high velocity" threshold. #driver_MSLUT0: 2863314260 #driver_MSLUT1: 1251300522 #driver_MSLUT2: 608774441 @@ -3782,11 +3776,8 @@ run_current: #high_velocity_threshold: # The velocity (in mm/s) to set the TMC driver internal "high # velocity" threshold (THIGH) to. This is typically used to disable -# the "CoolStep" feature at high speeds. Important - if -# high_velocity_threshold is set and "sensorless homing" is used, -# then one must ensure that the homing speed is below the high -# velocity threshold! The default is to not set a TMC "high -# velocity" threshold. +# the "CoolStep" feature at high speeds. The default is to not set a +# TMC "high velocity" threshold. #driver_MSLUT0: 2863314260 #driver_MSLUT1: 1251300522 #driver_MSLUT2: 608774441 diff --git a/klippy/extras/tmc.py b/klippy/extras/tmc.py index 1a5059fa83fb..a797b866e379 100644 --- a/klippy/extras/tmc.py +++ b/klippy/extras/tmc.py @@ -479,7 +479,7 @@ def __init__(self, config, mcu_tmc): self.diag_pin_field = None self.mcu_endstop = None self.en_pwm = False - self.pwmthrs = self.coolthrs = 0 + self.pwmthrs = self.coolthrs = self.thigh = 0 # Register virtual_endstop pin name_parts = config.get_name().split() ppins = self.printer.lookup_object("pins") @@ -503,8 +503,8 @@ def setup_pin(self, pin_type, pin_params): def handle_homing_move_begin(self, hmove): if self.mcu_endstop not in hmove.get_mcu_endstops(): return + # Enable/disable stealthchop self.pwmthrs = self.fields.get_field("tpwmthrs") - self.coolthrs = self.fields.get_field("tcoolthrs") reg = self.fields.lookup_register("en_pwm_mode", None) if reg is None: # On "stallguard4" drivers, "stealthchop" must be enabled @@ -518,12 +518,21 @@ def handle_homing_move_begin(self, hmove): self.fields.set_field("en_pwm_mode", 0) val = self.fields.set_field(self.diag_pin_field, 1) self.mcu_tmc.set_register("GCONF", val) + # Enable tcoolthrs (if not already) + self.coolthrs = self.fields.get_field("tcoolthrs") if self.coolthrs == 0: tc_val = self.fields.set_field("tcoolthrs", 0xfffff) self.mcu_tmc.set_register("TCOOLTHRS", tc_val) + # Disable thigh + reg = self.fields.lookup_register("thigh", None) + if reg is not None: + self.thigh = self.fields.get_field("thigh") + th_val = self.fields.set_field("thigh", 0) + self.mcu_tmc.set_register(reg, th_val) def handle_homing_move_end(self, hmove): if self.mcu_endstop not in hmove.get_mcu_endstops(): return + # Restore stealthchop/spreadcycle reg = self.fields.lookup_register("en_pwm_mode", None) if reg is None: tp_val = self.fields.set_field("tpwmthrs", self.pwmthrs) @@ -533,8 +542,14 @@ def handle_homing_move_end(self, hmove): self.fields.set_field("en_pwm_mode", self.en_pwm) val = self.fields.set_field(self.diag_pin_field, 0) self.mcu_tmc.set_register("GCONF", val) + # Restore tcoolthrs tc_val = self.fields.set_field("tcoolthrs", self.coolthrs) self.mcu_tmc.set_register("TCOOLTHRS", tc_val) + # Restore thigh + reg = self.fields.lookup_register("thigh", None) + if reg is not None: + th_val = self.fields.set_field("thigh", self.thigh) + self.mcu_tmc.set_register(reg, th_val) ###################################################################### From 5d52b32e645d71f8e6b8b5426f89fe61eaf92e56 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 16 May 2024 12:23:48 -0400 Subject: [PATCH 127/190] tmc: Remove code that could read microsteps in tmc config sections Setting of microsteps in the stepper config section has been required since commit eea0137b. Remove the no longer needed compatibility code. Signed-off-by: Kevin O'Connor --- klippy/extras/tmc.py | 10 +++------- klippy/stepper.py | 3 ++- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/klippy/extras/tmc.py b/klippy/extras/tmc.py index a797b866e379..1d8599e2ed2d 100644 --- a/klippy/extras/tmc.py +++ b/klippy/extras/tmc.py @@ -577,7 +577,7 @@ def TMCWaveTableHelper(config, mcu_tmc): set_config_field(config, "start_sin", 0) set_config_field(config, "start_sin90", 247) -# Helper to configure and query the microstep settings +# Helper to configure the microstep settings def TMCMicrostepHelper(config, mcu_tmc): fields = mcu_tmc.get_fields() stepper_name = " ".join(config.get_name().split()[1:]) @@ -585,13 +585,9 @@ def TMCMicrostepHelper(config, mcu_tmc): raise config.error( "Could not find config section '[%s]' required by tmc driver" % (stepper_name,)) - stepper_config = ms_config = config.getsection(stepper_name) - if (stepper_config.get('microsteps', None, note_valid=False) is None - and config.get('microsteps', None, note_valid=False) is not None): - # Older config format with microsteps in tmc config section - ms_config = config + sconfig = config.getsection(stepper_name) steps = {256: 0, 128: 1, 64: 2, 32: 3, 16: 4, 8: 5, 4: 6, 2: 7, 1: 8} - mres = ms_config.getchoice('microsteps', steps) + mres = sconfig.getchoice('microsteps', steps) fields.set_field("mres", mres) fields.set_field("intpol", config.getboolean("interpolate", True)) diff --git a/klippy/stepper.py b/klippy/stepper.py index 56c8ec758e17..9b692904dcf9 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -265,6 +265,7 @@ def parse_gear_ratio(config, note_valid): # Obtain "step distance" information from a config section def parse_step_distance(config, units_in_radians=None, note_valid=False): + # Check rotation_distance and gear_ratio if units_in_radians is None: # Caller doesn't know if units are in radians - infer it rd = config.get('rotation_distance', None, note_valid=False) @@ -276,7 +277,7 @@ def parse_step_distance(config, units_in_radians=None, note_valid=False): else: rotation_dist = config.getfloat('rotation_distance', above=0., note_valid=note_valid) - # Newer config format with rotation_distance + # Check microsteps and full_steps_per_rotation microsteps = config.getint('microsteps', minval=1, note_valid=note_valid) full_steps = config.getint('full_steps_per_rotation', 200, minval=1, note_valid=note_valid) From 236d780a0a3f00017e1a1193e036f0e0eb641437 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 16 May 2024 23:08:23 -0400 Subject: [PATCH 128/190] probe_eddy_current: Fix wait for samples in probing_move() Make sure to wait until all samples are available before performing analysis on the data. Signed-off-by: Kevin O'Connor --- klippy/extras/probe_eddy_current.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index 858fb6e09ea2..f670765c2ef1 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -260,20 +260,22 @@ def probing_move(self, pos, speed): trig_pos = phoming.probing_move(self, pos, speed) if not self._trigger_time: return trig_pos - # Wait for 200ms to elapse since trigger time + # Wait for samples to arrive + start_time = self._trigger_time + 0.050 + end_time = start_time + 0.100 reactor = self._printer.get_reactor() while 1: + if self._samples and self._samples[-1]['data'][-1][0] >= end_time: + break systime = reactor.monotonic() est_print_time = self._mcu.estimated_print_time(systime) - need_delay = self._trigger_time + 0.200 - est_print_time - if need_delay <= 0.: - break - reactor.pause(systime + need_delay) + if est_print_time > self._trigger_time + 1.0: + raise self._printer.command_error( + "probe_eddy_current sensor outage") + reactor.pause(systime + 0.010) # Find position since trigger samples = self._samples self._samples = [] - start_time = self._trigger_time + 0.050 - end_time = start_time + 0.100 samp_sum = 0. samp_count = 0 for msg in samples: From 29bfbd02f924c6cfc0f339129bc8ba302cabc4d4 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Sun, 21 Apr 2024 06:04:24 -0400 Subject: [PATCH 129/190] probe_eddy_current: fix attribute name Signed-off-by: Eric Callahan --- klippy/extras/probe_eddy_current.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index f670765c2ef1..73616af04cee 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -208,7 +208,7 @@ def _start_measurements(self, is_home=False): if self._is_sampling: return self._is_sampling = True - self._is_from_home = is_home + self._start_from_home = is_home self._sensor_helper.add_client(self._add_measurement) def _stop_measurements(self, is_home=False): if not self._is_sampling or (is_home and not self._start_from_home): From 6fac654352f0fbfb24dbfacadef9baf1aa79c2e9 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 30 Apr 2024 12:37:01 -0400 Subject: [PATCH 130/190] probe_eddy_current: Calibrate every 40um instead of 50um A 40um distance is more likely to be a full step distance on common Z leadscrews (which often use a rotation distance of 8mm). Signed-off-by: Kevin O'Connor --- klippy/extras/probe_eddy_current.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index 73616af04cee..8b9d705baea5 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -80,19 +80,19 @@ def handle_batch(msg): return True self.printer.lookup_object(self.name).add_client(handle_batch) toolhead.dwell(1.) - # Move to each 50um position - max_z = 4 - samp_dist = 0.050 - num_steps = int(max_z / samp_dist + .5) + 1 + # Move to each 40um position + max_z = 4.0 + samp_dist = 0.040 + req_zpos = [i*samp_dist for i in range(int(max_z / samp_dist) + 1)] start_pos = toolhead.get_position() times = [] - for i in range(num_steps): + for zpos in req_zpos: # Move to next position (always descending to reduce backlash) hop_pos = list(start_pos) - hop_pos[2] += i * samp_dist + 0.500 + hop_pos[2] += zpos + 0.500 move(hop_pos, move_speed) next_pos = list(start_pos) - next_pos[2] += i * samp_dist + next_pos[2] += zpos move(next_pos, move_speed) # Note sample timing start_query_time = toolhead.get_last_move_time() + 0.050 From 3dc7c9ab291ccbf9a90996705e3e460e680a1e8f Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 3 May 2024 13:21:21 -0400 Subject: [PATCH 131/190] test: Disable ldc1612 on stm32f042 build to reduce size Signed-off-by: Kevin O'Connor --- test/configs/stm32f042.config | 1 + 1 file changed, 1 insertion(+) diff --git a/test/configs/stm32f042.config b/test/configs/stm32f042.config index 7f1e879fbb18..12cc0922e45e 100644 --- a/test/configs/stm32f042.config +++ b/test/configs/stm32f042.config @@ -3,3 +3,4 @@ CONFIG_MACH_STM32=y CONFIG_MACH_STM32F042=y CONFIG_WANT_SOFTWARE_I2C=n CONFIG_WANT_LIS2DW=n +CONFIG_WANT_LDC1612=n From cb6cce3934bb37cd17d845b16640413b093bbe45 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 3 May 2024 11:20:05 -0400 Subject: [PATCH 132/190] sensor_ldc1612: Don't require DRDY bit to be set on data read It is not clear if DRDY is cleared during a STATUS read (which could occur from command_query_ldc1612_status() ). So, just check the "unread conversion" bit when reading data. Signed-off-by: Kevin O'Connor --- src/sensor_ldc1612.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sensor_ldc1612.c b/src/sensor_ldc1612.c index 9258ce6dc2c3..2e3f5694ef68 100644 --- a/src/sensor_ldc1612.c +++ b/src/sensor_ldc1612.c @@ -124,7 +124,7 @@ ldc1612_query(struct ldc1612 *ld, uint8_t oid) // Check if data available uint16_t status = read_reg_status(ld); - if (status != 0x48) + if (!(status & 0x08)) return; // Read coil0 frequency @@ -185,7 +185,7 @@ command_query_ldc1612_status(uint32_t *args) uint16_t status = read_reg_status(ld); uint32_t time2 = timer_read_time(); - uint32_t fifo = status == 0x48 ? BYTES_PER_SAMPLE : 0; + uint32_t fifo = status & 0x08 ? BYTES_PER_SAMPLE : 0; sensor_bulk_status(&ld->sb, args[0], time1, time2-time1, fifo); } DECL_COMMAND(command_query_ldc1612_status, "query_ldc1612_status oid=%c"); From 04c562941c65c0f62f39179c0c2cf4f580c6960f Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 3 May 2024 11:17:28 -0400 Subject: [PATCH 133/190] sensor_ldc1612: Add support for chips with INTB line routed to mcu If the INTB line is available it can reduce the MCU load. Signed-off-by: Kevin O'Connor --- docs/Config_Reference.md | 3 +++ klippy/extras/ldc1612.py | 13 ++++++++++-- src/sensor_ldc1612.c | 46 +++++++++++++++++++++++++++++++++------- 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 1338d46f989c..6b42fe48da91 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -2007,6 +2007,9 @@ Support for eddy current inductive probes. One may define this section sensor_type: ldc1612 # The sensor chip used to perform eddy current measurements. This # parameter must be provided and must be set to ldc1612. +#intb_pin: +# MCU gpio pin connected to the ldc1612 sensor's INTB pin (if +# available). The default is to not use the INTB pin. #z_offset: # The nominal distance (in mm) between the nozzle and bed that a # probing attempt should stop at. This parameter must be provided. diff --git a/klippy/extras/ldc1612.py b/klippy/extras/ldc1612.py index 2ae4dd7d7907..08cab965e752 100644 --- a/klippy/extras/ldc1612.py +++ b/klippy/extras/ldc1612.py @@ -87,8 +87,17 @@ def __init__(self, config, calibration=None): self.oid = oid = mcu.create_oid() self.query_ldc1612_cmd = None self.ldc1612_setup_home_cmd = self.query_ldc1612_home_state_cmd = None - mcu.add_config_cmd("config_ldc1612 oid=%d i2c_oid=%d" - % (oid, self.i2c.get_oid())) + if config.get('intb_pin', None) is not None: + ppins = config.get_printer().lookup_object("pins") + pin_params = ppins.lookup_pin(config.get('intb_pin')) + if pin_params['chip'] != mcu: + raise config.error("ldc1612 intb_pin must be on same mcu") + mcu.add_config_cmd( + "config_ldc1612_with_intb oid=%d i2c_oid=%d intb_pin=%s" + % (oid, self.i2c.get_oid(), pin_params['pin'])) + else: + mcu.add_config_cmd("config_ldc1612 oid=%d i2c_oid=%d" + % (oid, self.i2c.get_oid())) mcu.add_config_cmd("query_ldc1612 oid=%d rest_ticks=0" % (oid,), on_restart=True) mcu.register_config_callback(self._build_config) diff --git a/src/sensor_ldc1612.c b/src/sensor_ldc1612.c index 2e3f5694ef68..3db4de4b281e 100644 --- a/src/sensor_ldc1612.c +++ b/src/sensor_ldc1612.c @@ -17,7 +17,7 @@ #include "trsync.h" // trsync_do_trigger enum { - LDC_PENDING = 1<<0, + LDC_PENDING = 1<<0, LDC_HAVE_INTB = 1<<1, LH_AWAIT_HOMING = 1<<1, LH_CAN_TRIGGER = 1<<2 }; @@ -27,6 +27,7 @@ struct ldc1612 { struct i2cdev_s *i2c; uint8_t flags; struct sensor_bulk sb; + struct gpio_in intb_pin; // homing struct trsync *ts; uint8_t homing_flags; @@ -37,6 +38,13 @@ struct ldc1612 { static struct task_wake ldc1612_wake; +// Check if the intb line is "asserted" +static int +check_intb_asserted(struct ldc1612 *ld) +{ + return !gpio_in_read(ld->intb_pin); +} + // Query ldc1612 data static uint_fast8_t ldc1612_event(struct timer *timer) @@ -44,8 +52,10 @@ ldc1612_event(struct timer *timer) struct ldc1612 *ld = container_of(timer, struct ldc1612, timer); if (ld->flags & LDC_PENDING) ld->sb.possible_overflows++; - ld->flags |= LDC_PENDING; - sched_wake_task(&ldc1612_wake); + if (!(ld->flags & LDC_HAVE_INTB) || check_intb_asserted(ld)) { + ld->flags |= LDC_PENDING; + sched_wake_task(&ldc1612_wake); + } ld->timer.waketime += ld->rest_ticks; return SF_RESCHEDULE; } @@ -60,6 +70,17 @@ command_config_ldc1612(uint32_t *args) } DECL_COMMAND(command_config_ldc1612, "config_ldc1612 oid=%c i2c_oid=%c"); +void +command_config_ldc1612_with_intb(uint32_t *args) +{ + command_config_ldc1612(args); + struct ldc1612 *ld = oid_lookup(args[0], command_config_ldc1612); + ld->intb_pin = gpio_in_setup(args[2], 1); + ld->flags = LDC_HAVE_INTB; +} +DECL_COMMAND(command_config_ldc1612_with_intb, + "config_ldc1612_with_intb oid=%c i2c_oid=%c intb_pin=%c"); + void command_ldc1612_setup_home(uint32_t *args) { @@ -117,13 +138,11 @@ read_reg_status(struct ldc1612 *ld) static void ldc1612_query(struct ldc1612 *ld, uint8_t oid) { - // Clear pending flag + // Check if data available (and clear INTB line) + uint16_t status = read_reg_status(ld); irq_disable(); ld->flags &= ~LDC_PENDING; irq_enable(); - - // Check if data available - uint16_t status = read_reg_status(ld); if (!(status & 0x08)) return; @@ -161,7 +180,7 @@ command_query_ldc1612(uint32_t *args) struct ldc1612 *ld = oid_lookup(args[0], command_config_ldc1612); sched_del_timer(&ld->timer); - ld->flags = 0; + ld->flags &= ~LDC_PENDING; if (!args[1]) // End measurements return; @@ -181,6 +200,17 @@ command_query_ldc1612_status(uint32_t *args) { struct ldc1612 *ld = oid_lookup(args[0], command_config_ldc1612); + if (ld->flags & LDC_HAVE_INTB) { + // Check if a sample is pending in the chip via the intb line + irq_disable(); + uint32_t time = timer_read_time(); + int p = check_intb_asserted(ld); + irq_enable(); + sensor_bulk_status(&ld->sb, args[0], time, 0, p ? BYTES_PER_SAMPLE : 0); + return; + } + + // Query sensor to see if a sample is pending uint32_t time1 = timer_read_time(); uint16_t status = read_reg_status(ld); uint32_t time2 = timer_read_time(); From 4709f1fad50b642f744a8804608d55855d7d7b42 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 1 May 2024 20:46:49 -0400 Subject: [PATCH 134/190] sensor_ldc1612: Create new check_home() helper function Signed-off-by: Kevin O'Connor --- src/sensor_ldc1612.c | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/sensor_ldc1612.c b/src/sensor_ldc1612.c index 3db4de4b281e..f53b4e2ffff3 100644 --- a/src/sensor_ldc1612.c +++ b/src/sensor_ldc1612.c @@ -111,6 +111,26 @@ command_query_ldc1612_home_state(uint32_t *args) DECL_COMMAND(command_query_ldc1612_home_state, "query_ldc1612_home_state oid=%c"); +// Check if a sample should trigger a homing event +static void +check_home(struct ldc1612 *ld, uint32_t data) +{ + uint8_t homing_flags = ld->homing_flags; + if (!(homing_flags & LH_CAN_TRIGGER)) + return; + uint32_t time = timer_read_time(); + if ((homing_flags & LH_AWAIT_HOMING) + && timer_is_before(time, ld->homing_clock)) + return; + homing_flags &= ~LH_AWAIT_HOMING; + if (data > ld->trigger_threshold) { + homing_flags = 0; + ld->homing_clock = time; + trsync_do_trigger(ld->ts, ld->trigger_reason); + } + ld->homing_flags = homing_flags; +} + // Chip registers #define REG_DATA0_MSB 0x00 #define REG_DATA0_LSB 0x01 @@ -153,21 +173,8 @@ ldc1612_query(struct ldc1612 *ld, uint8_t oid) ld->sb.data_count += BYTES_PER_SAMPLE; // Check for endstop trigger - uint8_t homing_flags = ld->homing_flags; - if (homing_flags & LH_CAN_TRIGGER) { - uint32_t time = timer_read_time(); - if (!(homing_flags & LH_AWAIT_HOMING) - || !timer_is_before(time, ld->homing_clock)) { - homing_flags &= ~LH_AWAIT_HOMING; - uint32_t data = (d[0] << 24L) | (d[1] << 16L) | (d[2] << 8) | d[3]; - if (data > ld->trigger_threshold) { - homing_flags = 0; - ld->homing_clock = time; - trsync_do_trigger(ld->ts, ld->trigger_reason); - } - ld->homing_flags = homing_flags; - } - } + uint32_t data = (d[0] << 24L) | (d[1] << 16L) | (d[2] << 8) | d[3]; + check_home(ld, data); // Flush local buffer if needed if (ld->sb.data_count + BYTES_PER_SAMPLE > ARRAY_SIZE(ld->sb.data)) From 37482178b5482a013a4a5a62789705ccaf1e03c6 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 19 May 2024 13:15:30 -0400 Subject: [PATCH 135/190] mcu: Raise an error on a failed home_wait() call Raise a printer.command_error exception if a home_wait() call fails. This makes it easier to support future types of homing errors. Signed-off-by: Kevin O'Connor --- klippy/extras/bltouch.py | 6 +++++- klippy/extras/homing.py | 9 ++++++--- klippy/extras/probe_eddy_current.py | 5 +++-- klippy/mcu.py | 18 ++++++++++-------- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/klippy/extras/bltouch.py b/klippy/extras/bltouch.py index 49385428a452..b01cdb9ea897 100644 --- a/klippy/extras/bltouch.py +++ b/klippy/extras/bltouch.py @@ -116,7 +116,11 @@ def verify_state(self, triggered): self.mcu_endstop.home_start(self.action_end_time, ENDSTOP_SAMPLE_TIME, ENDSTOP_SAMPLE_COUNT, ENDSTOP_REST_TIME, triggered=triggered) - trigger_time = self.mcu_endstop.home_wait(self.action_end_time + 0.100) + try: + trigger_time = self.mcu_endstop.home_wait( + self.action_end_time + 0.100) + except self.printer.command_error as e: + return False return trigger_time > 0. def raise_probe(self): self.sync_mcu_print_time() diff --git a/klippy/extras/homing.py b/klippy/extras/homing.py index 634ad81b74c6..06b52f1ec56e 100644 --- a/klippy/extras/homing.py +++ b/klippy/extras/homing.py @@ -98,11 +98,14 @@ def homing_move(self, movepos, speed, probe_pos=False, trigger_times = {} move_end_print_time = self.toolhead.get_last_move_time() for mcu_endstop, name in self.endstops: - trigger_time = mcu_endstop.home_wait(move_end_print_time) + try: + trigger_time = mcu_endstop.home_wait(move_end_print_time) + except self.printer.command_error as e: + if error is None: + error = "Error during homing %s: %s" % (name, str(e)) + continue if trigger_time > 0.: trigger_times[name] = trigger_time - elif trigger_time < 0. and error is None: - error = "Communication timeout during homing %s" % (name,) elif check_triggered and error is None: error = "No trigger on %s after full movement" % (name,) # Determine stepper halt positions diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index 8b9d705baea5..636c800de795 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -243,8 +243,9 @@ def home_wait(self, home_end_time): trigger_time = self._sensor_helper.clear_home() self._stop_measurements(is_home=True) res = self._dispatch.stop() - if res == mcu.MCU_trsync.REASON_COMMS_TIMEOUT: - return -1. + if res >= mcu.MCU_trsync.REASON_COMMS_TIMEOUT: + raise self._printer.command_error( + "Communication timeout during homing") if res != mcu.MCU_trsync.REASON_ENDSTOP_HIT: return 0. if self._mcu.is_fileoutput(): diff --git a/klippy/mcu.py b/klippy/mcu.py index 2a01c5a7cc1f..d7a679acb114 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -104,9 +104,9 @@ def get_command_tag(self): class MCU_trsync: REASON_ENDSTOP_HIT = 1 - REASON_COMMS_TIMEOUT = 2 - REASON_HOST_REQUEST = 3 - REASON_PAST_END_TIME = 4 + REASON_HOST_REQUEST = 2 + REASON_PAST_END_TIME = 3 + REASON_COMMS_TIMEOUT = 4 def __init__(self, mcu, trdispatch): self._mcu = mcu self._trdispatch = trdispatch @@ -180,7 +180,7 @@ def _handle_trsync_state(self, params): if tc is not None: self._trigger_completion = None reason = params['trigger_reason'] - is_failure = (reason == self.REASON_COMMS_TIMEOUT) + is_failure = (reason >= self.REASON_COMMS_TIMEOUT) self._reactor.async_complete(tc, is_failure) elif self._home_end_clock is not None: clock = self._mcu.clock32_to_clock64(params['clock']) @@ -279,8 +279,9 @@ def stop(self): ffi_main, ffi_lib = chelper.get_ffi() ffi_lib.trdispatch_stop(self._trdispatch) res = [trsync.stop() for trsync in self._trsyncs] - if any([r == MCU_trsync.REASON_COMMS_TIMEOUT for r in res]): - return MCU_trsync.REASON_COMMS_TIMEOUT + err_res = [r for r in res if r >= MCU_trsync.REASON_COMMS_TIMEOUT] + if err_res: + return err_res[0] return res[0] class MCU_endstop: @@ -334,8 +335,9 @@ def home_wait(self, home_end_time): self._dispatch.wait_end(home_end_time) self._home_cmd.send([self._oid, 0, 0, 0, 0, 0, 0, 0]) res = self._dispatch.stop() - if res == MCU_trsync.REASON_COMMS_TIMEOUT: - return -1. + if res >= MCU_trsync.REASON_COMMS_TIMEOUT: + cmderr = self._mcu.get_printer().command_error + raise cmderr("Communication timeout during homing") if res != MCU_trsync.REASON_ENDSTOP_HIT: return 0. if self._mcu.is_fileoutput(): From 4a92727eab61349f2e24fd4296c7fb06740dffe9 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 1 May 2024 21:10:17 -0400 Subject: [PATCH 136/190] sensor_ldc1612: Halt homing if sensor reports a warning Explicitly check for sensor warnings during homing and report an error code back to the host. Signed-off-by: Kevin O'Connor --- klippy/extras/ldc1612.py | 9 +++++---- klippy/extras/probe_eddy_current.py | 9 ++++++--- src/sensor_ldc1612.c | 11 +++++++++-- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/klippy/extras/ldc1612.py b/klippy/extras/ldc1612.py index 08cab965e752..cdc01e04505e 100644 --- a/klippy/extras/ldc1612.py +++ b/klippy/extras/ldc1612.py @@ -121,7 +121,7 @@ def _build_config(self): oid=self.oid, cq=cmdqueue) self.ldc1612_setup_home_cmd = self.mcu.lookup_command( "ldc1612_setup_home oid=%c clock=%u threshold=%u" - " trsync_oid=%c trigger_reason=%c", cq=cmdqueue) + " trsync_oid=%c trigger_reason=%c error_reason=%c", cq=cmdqueue) self.query_ldc1612_home_state_cmd = self.mcu.lookup_query_command( "query_ldc1612_home_state oid=%c", "ldc1612_home_state oid=%c homing=%c trigger_clock=%u", @@ -138,13 +138,14 @@ def set_reg(self, reg, val, minclock=0): def add_client(self, cb): self.batch_bulk.add_client(cb) # Homing - def setup_home(self, print_time, trigger_freq, trsync_oid, reason): + def setup_home(self, print_time, trigger_freq, + trsync_oid, hit_reason, err_reason): clock = self.mcu.print_time_to_clock(print_time) tfreq = int(trigger_freq * (1<<28) / float(LDC1612_FREQ) + 0.5) self.ldc1612_setup_home_cmd.send( - [self.oid, clock, tfreq, trsync_oid, reason]) + [self.oid, clock, tfreq, trsync_oid, hit_reason, err_reason]) def clear_home(self): - self.ldc1612_setup_home_cmd.send([self.oid, 0, 0, 0, 0]) + self.ldc1612_setup_home_cmd.send([self.oid, 0, 0, 0, 0, 0]) if self.mcu.is_fileoutput(): return 0. params = self.query_ldc1612_home_state_cmd.send([self.oid]) diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index 636c800de795..3f4a5e20612c 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -185,6 +185,7 @@ def cmd_EDDY_CALIBRATE(self, gcmd): # Helper for implementing PROBE style commands class EddyEndstopWrapper: + REASON_SENSOR_ERROR = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 1 def __init__(self, config, sensor_helper, calibration): self._printer = config.get_printer() self._sensor_helper = sensor_helper @@ -236,7 +237,7 @@ def home_start(self, print_time, sample_time, sample_count, rest_time, trigger_completion = self._dispatch.start(print_time) self._sensor_helper.setup_home( print_time, trigger_freq, self._dispatch.get_oid(), - mcu.MCU_trsync.REASON_ENDSTOP_HIT) + mcu.MCU_trsync.REASON_ENDSTOP_HIT, self.REASON_SENSOR_ERROR) return trigger_completion def home_wait(self, home_end_time): self._dispatch.wait_end(home_end_time) @@ -244,8 +245,10 @@ def home_wait(self, home_end_time): self._stop_measurements(is_home=True) res = self._dispatch.stop() if res >= mcu.MCU_trsync.REASON_COMMS_TIMEOUT: - raise self._printer.command_error( - "Communication timeout during homing") + if res == mcu.MCU_trsync.REASON_COMMS_TIMEOUT: + raise self._printer.command_error( + "Communication timeout during homing") + raise self._printer.command_error("Eddy current sensor error") if res != mcu.MCU_trsync.REASON_ENDSTOP_HIT: return 0. if self._mcu.is_fileoutput(): diff --git a/src/sensor_ldc1612.c b/src/sensor_ldc1612.c index f53b4e2ffff3..6e52c177c07e 100644 --- a/src/sensor_ldc1612.c +++ b/src/sensor_ldc1612.c @@ -31,7 +31,7 @@ struct ldc1612 { // homing struct trsync *ts; uint8_t homing_flags; - uint8_t trigger_reason; + uint8_t trigger_reason, error_reason; uint32_t trigger_threshold; uint32_t homing_clock; }; @@ -95,11 +95,12 @@ command_ldc1612_setup_home(uint32_t *args) ld->homing_clock = args[1]; ld->ts = trsync_oid_lookup(args[3]); ld->trigger_reason = args[4]; + ld->error_reason = args[5]; ld->homing_flags = LH_AWAIT_HOMING | LH_CAN_TRIGGER; } DECL_COMMAND(command_ldc1612_setup_home, "ldc1612_setup_home oid=%c clock=%u threshold=%u" - " trsync_oid=%c trigger_reason=%c"); + " trsync_oid=%c trigger_reason=%c error_reason=%c"); void command_query_ldc1612_home_state(uint32_t *args) @@ -118,6 +119,12 @@ check_home(struct ldc1612 *ld, uint32_t data) uint8_t homing_flags = ld->homing_flags; if (!(homing_flags & LH_CAN_TRIGGER)) return; + if (data > 0x0fffffff) { + // Sensor reports an issue - cancel homing + ld->homing_flags = 0; + trsync_do_trigger(ld->ts, ld->error_reason); + return; + } uint32_t time = timer_read_time(); if ((homing_flags & LH_AWAIT_HOMING) && timer_is_before(time, ld->homing_clock)) From b6a0063235dd58bc9c1bb5767e87e09c9927f39a Mon Sep 17 00:00:00 2001 From: Timofey Titovets Date: Sun, 19 May 2024 01:52:36 +0200 Subject: [PATCH 137/190] tmc5160: csactual -> cs_actual Correct the name of "cs_actual" and correct the size on tmc5160. Signed-off-by: Timofey Titovets Signed-off-by: Kevin O'Connor --- klippy/extras/tmc2240.py | 2 +- klippy/extras/tmc5160.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/klippy/extras/tmc2240.py b/klippy/extras/tmc2240.py index 1e2a49157f36..21c835e26591 100644 --- a/klippy/extras/tmc2240.py +++ b/klippy/extras/tmc2240.py @@ -95,7 +95,7 @@ "s2vsb": 0x01 << 13, "stealth": 0x01 << 14, "fsactive": 0x01 << 15, - "csactual": 0x1F << 16, + "cs_actual": 0x1F << 16, "stallguard": 0x01 << 24, "ot": 0x01 << 25, "otpw": 0x01 << 26, diff --git a/klippy/extras/tmc5160.py b/klippy/extras/tmc5160.py index 02e16cd4d003..b773135c25d8 100644 --- a/klippy/extras/tmc5160.py +++ b/klippy/extras/tmc5160.py @@ -118,7 +118,7 @@ "s2vsb": 0x01 << 13, "stealth": 0x01 << 14, "fsactive": 0x01 << 15, - "csactual": 0xFF << 16, + "cs_actual": 0x1F << 16, "stallguard": 0x01 << 24, "ot": 0x01 << 25, "otpw": 0x01 << 26, From 3078912f1d9e063906b9b40eda73d63065900212 Mon Sep 17 00:00:00 2001 From: Elias Bakken Date: Sat, 25 May 2024 21:47:48 +0200 Subject: [PATCH 138/190] stm32: STM32F031 updates (#6607) Add support for STM32F031x6 which is the 32 KB version of the STM32F031 MCU. Add new I2C bus variant. Signed-off by: Elias Bakken --- src/stm32/Kconfig | 3 +-- src/stm32/stm32f0_i2c.c | 3 +++ test/configs/stm32f031.config | 8 ++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index dbd6ff959620..037e37bbe949 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -211,8 +211,7 @@ config CLOCK_FREQ config FLASH_SIZE hex - default 0x4000 if MACH_STM32F031 - default 0x8000 if MACH_STM32F042 + default 0x8000 if MACH_STM32F031 || MACH_STM32F042 default 0x20000 if MACH_STM32F070 || MACH_STM32F072 default 0x10000 if MACH_STM32F103 || MACH_STM32L412 # Flash size of stm32f103x8 (64KiB) default 0x40000 if MACH_STM32F2 || MACH_STM32F401 || MACH_STM32H723 diff --git a/src/stm32/stm32f0_i2c.c b/src/stm32/stm32f0_i2c.c index 1441079f73b8..597b48460eb7 100644 --- a/src/stm32/stm32f0_i2c.c +++ b/src/stm32/stm32f0_i2c.c @@ -22,6 +22,8 @@ struct i2c_info { DECL_CONSTANT_STR("BUS_PINS_i2c1_PF1_PF0", "PF1,PF0"); DECL_ENUMERATION("i2c_bus", "i2c1_PB8_PB9", 2); DECL_CONSTANT_STR("BUS_PINS_i2c1_PB8_PB9", "PB8,PB9"); + DECL_ENUMERATION("i2c_bus", "i2c1_PB8_PB7", 3); + DECL_CONSTANT_STR("BUS_PINS_i2c1_PB8_PB7", "PB8,PB7"); // Deprecated "i2c1a" style mappings DECL_ENUMERATION("i2c_bus", "i2c1", 0); DECL_CONSTANT_STR("BUS_PINS_i2c1", "PB6,PB7"); @@ -93,6 +95,7 @@ static const struct i2c_info i2c_bus[] = { { I2C1, GPIO('B', 6), GPIO('B', 7), GPIO_FUNCTION(1) }, { I2C1, GPIO('F', 1), GPIO('F', 0), GPIO_FUNCTION(1) }, { I2C1, GPIO('B', 8), GPIO('B', 9), GPIO_FUNCTION(1) }, + { I2C1, GPIO('B', 8), GPIO('B', 7), GPIO_FUNCTION(1) }, #elif CONFIG_MACH_STM32F7 { I2C1, GPIO('B', 6), GPIO('B', 7), GPIO_FUNCTION(1) }, #elif CONFIG_MACH_STM32G0 diff --git a/test/configs/stm32f031.config b/test/configs/stm32f031.config index aa9c282beb36..a8c95cfe7d70 100644 --- a/test/configs/stm32f031.config +++ b/test/configs/stm32f031.config @@ -1,5 +1,9 @@ -# Base config file for STM32F031 boards +# Base config file for STM32F031x4 (16KB) boards with serial on PA14/PA15 CONFIG_MACH_STM32=y CONFIG_MACH_STM32F031=y -CONFIG_WANT_GPIO_BITBANGING=n +CONFIG_LOW_LEVEL_OPTIONS=y +CONFIG_STM32_SERIAL_USART2_ALT_PA15_PA14=y +CONFIG_STM32_CLOCK_REF_INTERNAL=y +CONFIG_STM32_FLASH_START_0000=y CONFIG_WANT_DISPLAYS=n +CONFIG_WANT_GPIO_BITBANGING=n From 6cd174208bd9bbd51dc0d519a26661fb926d038a Mon Sep 17 00:00:00 2001 From: Jayofelony Date: Tue, 28 May 2024 02:57:42 +0200 Subject: [PATCH 139/190] config: Add Artillery Genius Pro config (#6604) Signed-off-by: Jeroen Oudshoorn --- config/printer-artillery-genius-pro-2022.cfg | 126 +++++++++++++++++++ test/klippy/printers.test | 1 + 2 files changed, 127 insertions(+) create mode 100644 config/printer-artillery-genius-pro-2022.cfg diff --git a/config/printer-artillery-genius-pro-2022.cfg b/config/printer-artillery-genius-pro-2022.cfg new file mode 100644 index 000000000000..aec7841c5e55 --- /dev/null +++ b/config/printer-artillery-genius-pro-2022.cfg @@ -0,0 +1,126 @@ +# This file contains pin mappings for the Artillery Genius Pro (2022) +# with a Artillery_Ruby-v1.2 board. To use this config, during "make menuconfig" +# select the STM32F401 with "No bootloader" and USB (on PA11/PA12) +# communication. + +# To flash this firmware, set the physical bridge between +3.3V and Boot0 PIN +# on Artillery_Ruby mainboard. Then run the command: +# make flash FLASH_DEVICE=/dev/serial/by-id/usb-Klipper_stm32f401xc_*-if00 + +# See docs/Config_Reference.md for a description of parameters. + +[extruder] +max_extrude_only_distance: 700.0 +step_pin: PA7 +dir_pin: PA6 +enable_pin: !PC4 +microsteps: 16 +rotation_distance: 7.1910 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PC9 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC0 +min_temp: 0 +max_temp: 250 +control: pid +pid_Kp: 23.223 +pid_Ki: 1.518 +pid_Kd: 88.826 + +[stepper_x] +step_pin: !PB14 +dir_pin: PB13 +enable_pin: !PB15 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PA2 +position_endstop: 0 +position_max: 220 +homing_speed: 60 + +[stepper_y] +step_pin: PB10 +dir_pin: PB2 +enable_pin: !PB12 +microsteps: 16 +rotation_distance: 40 +endstop_pin: !PA1 +position_endstop: 0 +position_max: 220 +homing_speed: 60 + +[stepper_z] +step_pin: PB0 +dir_pin: !PC5 +enable_pin: !PB1 +microsteps: 16 +rotation_distance: 8 +endstop_pin: probe:z_virtual_endstop +position_max: 250 +position_min: -5 + +[heater_bed] +heater_pin: PA8 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC1 +min_temp: 0 +max_temp: 130 +control: pid +pid_Kp: 23.223 +pid_Ki: 1.518 +pid_Kd: 88.826 + +[bed_screws] +screw1: 38,45 +screw2: 180,45 +screw3: 180,180 +screw4: 38,180 + +[fan] +pin: PC8 +off_below: 0.1 + +[heater_fan hotend_fan] +pin: PC7 +heater: extruder +heater_temp: 50.0 + +[controller_fan stepper_fan] +pin: PC6 +idle_timeout: 300 + +[mcu] +serial: /dev/serial/by-id/usb-Klipper_stm32f401xc_ + +[printer] +kinematics: cartesian +max_velocity: 500 +max_accel: 4000 +max_z_velocity: 50 +square_corner_velocity: 5.0 +max_z_accel: 100 + +[bltouch] +sensor_pin: PC2 +control_pin: PC3 +x_offset:27.25 +y_offset:-12.8 +z_offset: 0.25 +speed:10 +samples:1 +samples_result:average + +[bed_mesh] +speed: 800 +mesh_min: 30, 20 +mesh_max: 210, 200 +probe_count: 5,5 +algorithm: bicubic +move_check_distance: 3.0 + +[safe_z_home] +home_xy_position: 110,110 +speed: 100 +z_hop: 10 +z_hop_speed: 5 diff --git a/test/klippy/printers.test b/test/klippy/printers.test index e404f6f153d0..ba7adb614f0c 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -204,6 +204,7 @@ CONFIG ../../config/printer-voxelab-aquila-2021.cfg # Printers using the stm32f401 DICTIONARY stm32f401.dict CONFIG ../../config/generic-fysetc-cheetah-v2.0.cfg +CONFIG ../../config/printer-artillery-genius-pro-2022.cfg CONFIG ../../config/printer-artillery-sidewinder-x2-2022.cfg CONFIG ../../config/printer-artillery-sidewinder-x3-plus-2024.cfg CONFIG ../../config/printer-creality-ender5-s1-2023.cfg From 49c0ad6369670da574f550aa878ce9f6e1899e74 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 4 Jun 2024 15:49:09 -0400 Subject: [PATCH 140/190] motan: Fix logic error resulting in incorrect stepper phase graphing The mcu_phase_offset should be added not subtracted. Signed-off-by: Kevin O'Connor --- scripts/motan/readlog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/motan/readlog.py b/scripts/motan/readlog.py index 1b44c9375674..48284ec2be05 100644 --- a/scripts/motan/readlog.py +++ b/scripts/motan/readlog.py @@ -293,7 +293,7 @@ def pull_data(self, req_time): self._pull_block(req_time) continue step_pos = step_data[data_pos][1] - return (step_pos - self.mcu_phase_offset) % self.phases + return (step_pos + self.mcu_phase_offset) % self.phases def _pull_block(self, req_time): step_data = self.step_data del step_data[:-1] From 12f92c55f1c956bf415b379828c7b0bafae35026 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 27 May 2024 13:33:17 -0400 Subject: [PATCH 141/190] probe: Code movement in probe.py Move code around in probe.py and add some comments. No code changes. Signed-off-by: Kevin O'Connor --- klippy/extras/probe.py | 155 ++++++++++++++++++++++------------------- 1 file changed, 85 insertions(+), 70 deletions(-) diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index 073c875cc3ea..1a04a61efd21 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -13,6 +13,11 @@ can travel further (the Z minimum position can be negative). """ + +###################################################################### +# Probe device implementation helpers +###################################################################### + class PrinterProbe: def __init__(self, config, mcu_probe): self.printer = config.get_printer() @@ -290,76 +295,10 @@ def cmd_Z_OFFSET_APPLY_PROBE(self,gcmd): configfile.set(self.name, 'z_offset', "%.3f" % (new_calibrate,)) cmd_Z_OFFSET_APPLY_PROBE_help = "Adjust the probe's z_offset" -# Endstop wrapper that enables probe specific features -class ProbeEndstopWrapper: - def __init__(self, config): - self.printer = config.get_printer() - self.position_endstop = config.getfloat('z_offset') - self.stow_on_each_sample = config.getboolean( - 'deactivate_on_each_sample', True) - gcode_macro = self.printer.load_object(config, 'gcode_macro') - self.activate_gcode = gcode_macro.load_template( - config, 'activate_gcode', '') - self.deactivate_gcode = gcode_macro.load_template( - config, 'deactivate_gcode', '') - # Create an "endstop" object to handle the probe pin - ppins = self.printer.lookup_object('pins') - pin = config.get('pin') - pin_params = ppins.lookup_pin(pin, can_invert=True, can_pullup=True) - mcu = pin_params['chip'] - self.mcu_endstop = mcu.setup_pin('endstop', pin_params) - self.printer.register_event_handler('klippy:mcu_identify', - self._handle_mcu_identify) - # Wrappers - self.get_mcu = self.mcu_endstop.get_mcu - self.add_stepper = self.mcu_endstop.add_stepper - self.get_steppers = self.mcu_endstop.get_steppers - self.home_start = self.mcu_endstop.home_start - self.home_wait = self.mcu_endstop.home_wait - self.query_endstop = self.mcu_endstop.query_endstop - # multi probes state - self.multi = 'OFF' - def _handle_mcu_identify(self): - kin = self.printer.lookup_object('toolhead').get_kinematics() - for stepper in kin.get_steppers(): - if stepper.is_active_axis('z'): - self.add_stepper(stepper) - def _raise_probe(self): - toolhead = self.printer.lookup_object('toolhead') - start_pos = toolhead.get_position() - self.deactivate_gcode.run_gcode_from_command() - if toolhead.get_position()[:3] != start_pos[:3]: - raise self.printer.command_error( - "Toolhead moved during probe deactivate_gcode script") - def _lower_probe(self): - toolhead = self.printer.lookup_object('toolhead') - start_pos = toolhead.get_position() - self.activate_gcode.run_gcode_from_command() - if toolhead.get_position()[:3] != start_pos[:3]: - raise self.printer.command_error( - "Toolhead moved during probe activate_gcode script") - def multi_probe_begin(self): - if self.stow_on_each_sample: - return - self.multi = 'FIRST' - def multi_probe_end(self): - if self.stow_on_each_sample: - return - self._raise_probe() - self.multi = 'OFF' - def probing_move(self, pos, speed): - phoming = self.printer.lookup_object('homing') - return phoming.probing_move(self, pos, speed) - def probe_prepare(self, hmove): - if self.multi == 'OFF' or self.multi == 'FIRST': - self._lower_probe() - if self.multi == 'FIRST': - self.multi = 'ON' - def probe_finish(self, hmove): - if self.multi == 'OFF': - self._raise_probe() - def get_position_endstop(self): - return self.position_endstop + +###################################################################### +# Tools for utilizing the probe +###################################################################### # Helper code that can probe a series of points and report the # position at each point. @@ -456,5 +395,81 @@ def _manual_probe_finalize(self, kin_pos): self.results.append(kin_pos) self._manual_probe_start() + +###################################################################### +# Handle [probe] config +###################################################################### + +# Endstop wrapper that enables probe specific features +class ProbeEndstopWrapper: + def __init__(self, config): + self.printer = config.get_printer() + self.position_endstop = config.getfloat('z_offset') + self.stow_on_each_sample = config.getboolean( + 'deactivate_on_each_sample', True) + gcode_macro = self.printer.load_object(config, 'gcode_macro') + self.activate_gcode = gcode_macro.load_template( + config, 'activate_gcode', '') + self.deactivate_gcode = gcode_macro.load_template( + config, 'deactivate_gcode', '') + # Create an "endstop" object to handle the probe pin + ppins = self.printer.lookup_object('pins') + pin = config.get('pin') + pin_params = ppins.lookup_pin(pin, can_invert=True, can_pullup=True) + mcu = pin_params['chip'] + self.mcu_endstop = mcu.setup_pin('endstop', pin_params) + self.printer.register_event_handler('klippy:mcu_identify', + self._handle_mcu_identify) + # Wrappers + self.get_mcu = self.mcu_endstop.get_mcu + self.add_stepper = self.mcu_endstop.add_stepper + self.get_steppers = self.mcu_endstop.get_steppers + self.home_start = self.mcu_endstop.home_start + self.home_wait = self.mcu_endstop.home_wait + self.query_endstop = self.mcu_endstop.query_endstop + # multi probes state + self.multi = 'OFF' + def _handle_mcu_identify(self): + kin = self.printer.lookup_object('toolhead').get_kinematics() + for stepper in kin.get_steppers(): + if stepper.is_active_axis('z'): + self.add_stepper(stepper) + def _raise_probe(self): + toolhead = self.printer.lookup_object('toolhead') + start_pos = toolhead.get_position() + self.deactivate_gcode.run_gcode_from_command() + if toolhead.get_position()[:3] != start_pos[:3]: + raise self.printer.command_error( + "Toolhead moved during probe deactivate_gcode script") + def _lower_probe(self): + toolhead = self.printer.lookup_object('toolhead') + start_pos = toolhead.get_position() + self.activate_gcode.run_gcode_from_command() + if toolhead.get_position()[:3] != start_pos[:3]: + raise self.printer.command_error( + "Toolhead moved during probe activate_gcode script") + def multi_probe_begin(self): + if self.stow_on_each_sample: + return + self.multi = 'FIRST' + def multi_probe_end(self): + if self.stow_on_each_sample: + return + self._raise_probe() + self.multi = 'OFF' + def probing_move(self, pos, speed): + phoming = self.printer.lookup_object('homing') + return phoming.probing_move(self, pos, speed) + def probe_prepare(self, hmove): + if self.multi == 'OFF' or self.multi == 'FIRST': + self._lower_probe() + if self.multi == 'FIRST': + self.multi = 'ON' + def probe_finish(self, hmove): + if self.multi == 'OFF': + self._raise_probe() + def get_position_endstop(self): + return self.position_endstop + def load_config(config): return PrinterProbe(config, ProbeEndstopWrapper(config)) From bec47e049278e9745173ebdb8b184ff3cf4d98c2 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 23 May 2024 13:32:04 -0400 Subject: [PATCH 142/190] probe: Split out new ProbeSessionHelper() class from PrinterProbe() Separate out the PrinterProbe() class to make the external probe interfaces more clear. Signed-off-by: Kevin O'Connor --- klippy/extras/axis_twist_compensation.py | 3 ++- klippy/extras/probe.py | 30 ++++++++++++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/klippy/extras/axis_twist_compensation.py b/klippy/extras/axis_twist_compensation.py index ad08ad5593c7..cd3b8417729b 100644 --- a/klippy/extras/axis_twist_compensation.py +++ b/klippy/extras/axis_twist_compensation.py @@ -186,7 +186,8 @@ def _calibration(self, probe_points, nozzle_points, interval): probe_points[self.current_point_index][1], None)) # probe the point - self.current_measured_z = self.probe.run_probe(self.gcmd)[2] + pos = probe.run_single_probe(self.probe, self.gcmd) + self.current_measured_z = pos[2] # horizontal_move_z (to prevent probe trigger or hitting bed) self._move_helper((None, None, self.horizontal_move_z)) diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index 1a04a61efd21..80a6d4e75a04 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -18,7 +18,8 @@ # Probe device implementation helpers ###################################################################### -class PrinterProbe: +# Helper to track multiple probe attempts in a single command +class ProbeSessionHelper: def __init__(self, config, mcu_probe): self.printer = config.get_printer() self.name = config.get_name() @@ -375,14 +376,15 @@ def start_probe(self, gcmd): if self.horizontal_move_z < self.probe_offsets[2]: raise gcmd.error("horizontal_move_z can't be less than" " probe's z_offset") - probe.multi_probe_begin() + probe_session = probe.start_probe_session(gcmd) + probe_session.multi_probe_begin() while 1: done = self._move_next() if done: break - pos = probe.run_probe(gcmd) + pos = probe_session.run_probe(gcmd) self.results.append(pos) - probe.multi_probe_end() + probe_session.multi_probe_end() def _manual_probe_start(self): done = self._move_next() if not done: @@ -395,6 +397,12 @@ def _manual_probe_finalize(self, kin_pos): self.results.append(kin_pos) self._manual_probe_start() +# Helper to obtain a single probe measurement +def run_single_probe(probe, gcmd): + probe_session = probe.start_probe_session(gcmd) + pos = probe_session.run_probe(gcmd) + return pos + ###################################################################### # Handle [probe] config @@ -471,5 +479,19 @@ def probe_finish(self, hmove): def get_position_endstop(self): return self.position_endstop +# Main external probe interface +class PrinterProbe: + def __init__(self, config, mcu_probe): + self.printer = config.get_printer() + self.probe_session = ProbeSessionHelper(config, mcu_probe) + def get_lift_speed(self, gcmd=None): + return self.probe_session.get_lift_speed(gcmd) + def get_offsets(self): + return self.probe_session.get_offsets() + def get_status(self, eventtime): + return self.probe_session.get_status(eventtime) + def start_probe_session(self, gcmd): + return self.probe_session + def load_config(config): return PrinterProbe(config, ProbeEndstopWrapper(config)) From 8fc11b4a2e0dd33cd99e4d3c8ce4107acf4772ff Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 22 May 2024 20:59:28 -0400 Subject: [PATCH 143/190] probe: Introduce new ProbeCommandHelper class Move the PROBE and QUERY_PROBE commands from ProbeSessionHelper class to new ProbeCommandHelper class. Signed-off-by: Kevin O'Connor --- klippy/extras/probe.py | 61 ++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index 80a6d4e75a04..c7e838aa8952 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -18,6 +18,41 @@ # Probe device implementation helpers ###################################################################### +# Helper to implement common probing commands +class ProbeCommandHelper: + def __init__(self, config, probe, query_endstop=None): + self.printer = config.get_printer() + self.probe = probe + self.query_endstop = query_endstop + self.name = config.get_name() + gcode = self.printer.lookup_object('gcode') + # QUERY_PROBE command + self.last_state = False + gcode.register_command('QUERY_PROBE', self.cmd_QUERY_PROBE, + desc=self.cmd_QUERY_PROBE_help) + # PROBE command + self.last_z_result = 0. + gcode.register_command('PROBE', self.cmd_PROBE, + desc=self.cmd_PROBE_help) + def get_status(self, eventtime): + return {'name': self.name, + 'last_query': self.last_state, + 'last_z_result': self.last_z_result} + cmd_QUERY_PROBE_help = "Return the status of the z-probe" + def cmd_QUERY_PROBE(self, gcmd): + if self.query_endstop is None: + raise gcmd.error("Probe does not support QUERY_PROBE") + toolhead = self.printer.lookup_object('toolhead') + print_time = toolhead.get_last_move_time() + res = self.query_endstop(print_time) + self.last_state = res + gcmd.respond_info("probe: %s" % (["open", "TRIGGERED"][not not res],)) + cmd_PROBE_help = "Probe Z-height at current XY position" + def cmd_PROBE(self, gcmd): + pos = run_single_probe(self.probe, gcmd) + gcmd.respond_info("Result is z=%.6f" % (pos[2],)) + self.last_z_result = pos[2] + # Helper to track multiple probe attempts in a single command class ProbeSessionHelper: def __init__(self, config, mcu_probe): @@ -31,8 +66,6 @@ def __init__(self, config, mcu_probe): self.z_offset = config.getfloat('z_offset') self.probe_calibrate_z = 0. self.multi_probe_pending = False - self.last_state = False - self.last_z_result = 0. self.gcode_move = self.printer.load_object(config, "gcode_move") # Infer Z position to move to during a probe if config.has_section('stepper_z'): @@ -69,10 +102,6 @@ def __init__(self, config, mcu_probe): self._handle_command_error) # Register PROBE/QUERY_PROBE commands self.gcode = self.printer.lookup_object('gcode') - self.gcode.register_command('PROBE', self.cmd_PROBE, - desc=self.cmd_PROBE_help) - self.gcode.register_command('QUERY_PROBE', self.cmd_QUERY_PROBE, - desc=self.cmd_QUERY_PROBE_help) self.gcode.register_command('PROBE_CALIBRATE', self.cmd_PROBE_CALIBRATE, desc=self.cmd_PROBE_CALIBRATE_help) self.gcode.register_command('PROBE_ACCURACY', self.cmd_PROBE_ACCURACY, @@ -196,22 +225,6 @@ def run_probe(self, gcmd): if samples_result == 'median': return self._calc_median(positions) return self._calc_mean(positions) - cmd_PROBE_help = "Probe Z-height at current XY position" - def cmd_PROBE(self, gcmd): - pos = self.run_probe(gcmd) - gcmd.respond_info("Result is z=%.6f" % (pos[2],)) - self.last_z_result = pos[2] - cmd_QUERY_PROBE_help = "Return the status of the z-probe" - def cmd_QUERY_PROBE(self, gcmd): - toolhead = self.printer.lookup_object('toolhead') - print_time = toolhead.get_last_move_time() - res = self.mcu_probe.query_endstop(print_time) - self.last_state = res - gcmd.respond_info("probe: %s" % (["open", "TRIGGERED"][not not res],)) - def get_status(self, eventtime): - return {'name': self.name, - 'last_query': self.last_state, - 'last_z_result': self.last_z_result} cmd_PROBE_ACCURACY_help = "Probe Z-height accuracy at current XY position" def cmd_PROBE_ACCURACY(self, gcmd): speed = gcmd.get_float("PROBE_SPEED", self.speed, above=0.) @@ -483,13 +496,15 @@ def get_position_endstop(self): class PrinterProbe: def __init__(self, config, mcu_probe): self.printer = config.get_printer() + self.cmd_helper = ProbeCommandHelper(config, self, + mcu_probe.query_endstop) self.probe_session = ProbeSessionHelper(config, mcu_probe) def get_lift_speed(self, gcmd=None): return self.probe_session.get_lift_speed(gcmd) def get_offsets(self): return self.probe_session.get_offsets() def get_status(self, eventtime): - return self.probe_session.get_status(eventtime) + return self.cmd_helper.get_status(eventtime) def start_probe_session(self, gcmd): return self.probe_session From 6f6122a576c42852ca8835f9cef5993d2ac76a1d Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 23 May 2024 13:51:04 -0400 Subject: [PATCH 144/190] probe: Move Z_OFFSET_APPLY_PROBE to ProbeCommandHelper class Signed-off-by: Kevin O'Connor --- klippy/extras/probe.py | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index c7e838aa8952..103a35862fe7 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -34,6 +34,10 @@ def __init__(self, config, probe, query_endstop=None): self.last_z_result = 0. gcode.register_command('PROBE', self.cmd_PROBE, desc=self.cmd_PROBE_help) + # Other commands + gcode.register_command('Z_OFFSET_APPLY_PROBE', + self.cmd_Z_OFFSET_APPLY_PROBE, + desc=self.cmd_Z_OFFSET_APPLY_PROBE_help) def get_status(self, eventtime): return {'name': self.name, 'last_query': self.last_state, @@ -52,6 +56,22 @@ def cmd_PROBE(self, gcmd): pos = run_single_probe(self.probe, gcmd) gcmd.respond_info("Result is z=%.6f" % (pos[2],)) self.last_z_result = pos[2] + cmd_Z_OFFSET_APPLY_PROBE_help = "Adjust the probe's z_offset" + def cmd_Z_OFFSET_APPLY_PROBE(self, gcmd): + gcode_move = self.printer.lookup_object("gcode_move") + offset = gcode_move.get_status()['homing_origin'].z + if offset == 0: + gcmd.respond_info("Nothing to do: Z Offset is 0") + return + z_offset = self.probe.get_offsets()[2] + new_calibrate = z_offset - offset + gcmd.respond_info( + "%s: z_offset: %.3f\n" + "The SAVE_CONFIG command will update the printer config file\n" + "with the above and restart the printer." + % (self.name, new_calibrate)) + configfile = self.printer.lookup_object('configfile') + configfile.set(self.name, 'z_offset', "%.3f" % (new_calibrate,)) # Helper to track multiple probe attempts in a single command class ProbeSessionHelper: @@ -66,7 +86,6 @@ def __init__(self, config, mcu_probe): self.z_offset = config.getfloat('z_offset') self.probe_calibrate_z = 0. self.multi_probe_pending = False - self.gcode_move = self.printer.load_object(config, "gcode_move") # Infer Z position to move to during a probe if config.has_section('stepper_z'): zconfig = config.getsection('stepper_z') @@ -106,9 +125,6 @@ def __init__(self, config, mcu_probe): desc=self.cmd_PROBE_CALIBRATE_help) self.gcode.register_command('PROBE_ACCURACY', self.cmd_PROBE_ACCURACY, desc=self.cmd_PROBE_ACCURACY_help) - self.gcode.register_command('Z_OFFSET_APPLY_PROBE', - self.cmd_Z_OFFSET_APPLY_PROBE, - desc=self.cmd_Z_OFFSET_APPLY_PROBE_help) def _handle_homing_move_begin(self, hmove): if self.mcu_probe in hmove.get_mcu_endstops(): self.mcu_probe.probe_prepare(hmove) @@ -294,20 +310,6 @@ def cmd_PROBE_CALIBRATE(self, gcmd): # Start manual probe manual_probe.ManualProbeHelper(self.printer, gcmd, self.probe_calibrate_finalize) - def cmd_Z_OFFSET_APPLY_PROBE(self,gcmd): - offset = self.gcode_move.get_status()['homing_origin'].z - configfile = self.printer.lookup_object('configfile') - if offset == 0: - self.gcode.respond_info("Nothing to do: Z Offset is 0") - else: - new_calibrate = self.z_offset - offset - self.gcode.respond_info( - "%s: z_offset: %.3f\n" - "The SAVE_CONFIG command will update the printer config file\n" - "with the above and restart the printer." - % (self.name, new_calibrate)) - configfile.set(self.name, 'z_offset', "%.3f" % (new_calibrate,)) - cmd_Z_OFFSET_APPLY_PROBE_help = "Adjust the probe's z_offset" ###################################################################### From 6ea5b94d1e3add2bb15cbe9c2f5fd9c7da2e0970 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 23 May 2024 14:51:01 -0400 Subject: [PATCH 145/190] probe: Convert probe.get_lift_speed() to probe.get_print_params() Add a get_print_params() method that can extract all the common probing parameters. Replace get_lift_speed() with this more general function. Signed-off-by: Kevin O'Connor --- klippy/extras/axis_twist_compensation.py | 2 +- klippy/extras/probe.py | 73 +++++++++++++----------- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/klippy/extras/axis_twist_compensation.py b/klippy/extras/axis_twist_compensation.py index cd3b8417729b..e01951abb358 100644 --- a/klippy/extras/axis_twist_compensation.py +++ b/klippy/extras/axis_twist_compensation.py @@ -95,7 +95,7 @@ def _handle_connect(self): config = self.printer.lookup_object('configfile') raise config.error( "AXIS_TWIST_COMPENSATION requires [probe] to be defined") - self.lift_speed = self.probe.get_lift_speed() + self.lift_speed = self.probe.get_probe_params()['lift_speed'] self.probe_x_offset, self.probe_y_offset, _ = \ self.probe.get_offsets() diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index 103a35862fe7..c1ee40d99cee 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -125,6 +125,7 @@ def __init__(self, config, mcu_probe): desc=self.cmd_PROBE_CALIBRATE_help) self.gcode.register_command('PROBE_ACCURACY', self.cmd_PROBE_ACCURACY, desc=self.cmd_PROBE_ACCURACY_help) + self.dummy_gcode_cmd = self.gcode.create_gcode_command("", "", {}) def _handle_homing_move_begin(self, hmove): if self.mcu_probe in hmove.get_mcu_endstops(): self.mcu_probe.probe_prepare(hmove) @@ -157,10 +158,26 @@ def setup_pin(self, pin_type, pin_params): if pin_params['invert'] or pin_params['pullup']: raise pins.error("Can not pullup/invert probe virtual endstop") return self.mcu_probe - def get_lift_speed(self, gcmd=None): - if gcmd is not None: - return gcmd.get_float("LIFT_SPEED", self.lift_speed, above=0.) - return self.lift_speed + def get_probe_params(self, gcmd=None): + if gcmd is None: + gcmd = self.dummy_gcode_cmd + probe_speed = gcmd.get_float("PROBE_SPEED", self.speed, above=0.) + lift_speed = gcmd.get_float("LIFT_SPEED", self.lift_speed, above=0.) + samples = gcmd.get_int("SAMPLES", self.sample_count, minval=1) + sample_retract_dist = gcmd.get_float("SAMPLE_RETRACT_DIST", + self.sample_retract_dist, above=0.) + samples_tolerance = gcmd.get_float("SAMPLES_TOLERANCE", + self.samples_tolerance, minval=0.) + samples_retries = gcmd.get_int("SAMPLES_TOLERANCE_RETRIES", + self.samples_retries, minval=0) + samples_result = gcmd.get("SAMPLES_RESULT", self.samples_result) + return {'probe_speed': probe_speed, + 'lift_speed': lift_speed, + 'samples': samples, + 'sample_retract_dist': sample_retract_dist, + 'samples_tolerance': samples_tolerance, + 'samples_tolerance_retries': samples_retries, + 'samples_result': samples_result} def get_offsets(self): return self.x_offset, self.y_offset, self.z_offset def _probe(self, speed): @@ -204,68 +221,58 @@ def _calc_median(self, positions): # even number of samples return self._calc_mean(z_sorted[middle-1:middle+1]) def run_probe(self, gcmd): - speed = gcmd.get_float("PROBE_SPEED", self.speed, above=0.) - lift_speed = self.get_lift_speed(gcmd) - sample_count = gcmd.get_int("SAMPLES", self.sample_count, minval=1) - sample_retract_dist = gcmd.get_float("SAMPLE_RETRACT_DIST", - self.sample_retract_dist, above=0.) - samples_tolerance = gcmd.get_float("SAMPLES_TOLERANCE", - self.samples_tolerance, minval=0.) - samples_retries = gcmd.get_int("SAMPLES_TOLERANCE_RETRIES", - self.samples_retries, minval=0) - samples_result = gcmd.get("SAMPLES_RESULT", self.samples_result) + params = self.get_probe_params(gcmd) must_notify_multi_probe = not self.multi_probe_pending if must_notify_multi_probe: self.multi_probe_begin() probexy = self.printer.lookup_object('toolhead').get_position()[:2] retries = 0 positions = [] + sample_count = params['samples'] while len(positions) < sample_count: # Probe position - pos = self._probe(speed) + pos = self._probe(params['probe_speed']) positions.append(pos) # Check samples tolerance z_positions = [p[2] for p in positions] - if max(z_positions) - min(z_positions) > samples_tolerance: - if retries >= samples_retries: + if max(z_positions)-min(z_positions) > params['samples_tolerance']: + if retries >= params['samples_tolerance_retries']: raise gcmd.error("Probe samples exceed samples_tolerance") gcmd.respond_info("Probe samples exceed tolerance. Retrying...") retries += 1 positions = [] # Retract if len(positions) < sample_count: - self._move(probexy + [pos[2] + sample_retract_dist], lift_speed) + self._move(probexy + [pos[2] + params['sample_retract_dist']], + params['lift_speed']) if must_notify_multi_probe: self.multi_probe_end() # Calculate and return result - if samples_result == 'median': + if params['samples_result'] == 'median': return self._calc_median(positions) return self._calc_mean(positions) cmd_PROBE_ACCURACY_help = "Probe Z-height accuracy at current XY position" def cmd_PROBE_ACCURACY(self, gcmd): - speed = gcmd.get_float("PROBE_SPEED", self.speed, above=0.) - lift_speed = self.get_lift_speed(gcmd) + params = self.get_probe_params(gcmd) sample_count = gcmd.get_int("SAMPLES", 10, minval=1) - sample_retract_dist = gcmd.get_float("SAMPLE_RETRACT_DIST", - self.sample_retract_dist, above=0.) toolhead = self.printer.lookup_object('toolhead') pos = toolhead.get_position() gcmd.respond_info("PROBE_ACCURACY at X:%.3f Y:%.3f Z:%.3f" " (samples=%d retract=%.3f" " speed=%.1f lift_speed=%.1f)\n" % (pos[0], pos[1], pos[2], - sample_count, sample_retract_dist, - speed, lift_speed)) + sample_count, params['sample_retract_dist'], + params['probe_speed'], params['lift_speed'])) # Probe bed sample_count times self.multi_probe_begin() positions = [] while len(positions) < sample_count: # Probe position - pos = self._probe(speed) + pos = self._probe(params['probe_speed']) positions.append(pos) # Retract - liftpos = [None, None, pos[2] + sample_retract_dist] - self._move(liftpos, lift_speed) + liftpos = [None, None, pos[2] + params['sample_retract_dist']] + self._move(liftpos, params['lift_speed']) self.multi_probe_end() # Calculate maximum, minimum and average values max_value = max([p[2] for p in positions]) @@ -297,12 +304,12 @@ def probe_calibrate_finalize(self, kin_pos): def cmd_PROBE_CALIBRATE(self, gcmd): manual_probe.verify_no_manual_probe(self.printer) # Perform initial probe - lift_speed = self.get_lift_speed(gcmd) + params = self.get_probe_params(gcmd) curpos = self.run_probe(gcmd) # Move away from the bed self.probe_calibrate_z = curpos[2] curpos[2] += 5. - self._move(curpos, lift_speed) + self._move(curpos, params['lift_speed']) # Move the nozzle over the probe point curpos[0] += self.x_offset curpos[1] += self.y_offset @@ -386,7 +393,7 @@ def start_probe(self, gcmd): self._manual_probe_start() return # Perform automatic probing - self.lift_speed = probe.get_lift_speed(gcmd) + self.lift_speed = probe.get_probe_params(gcmd)['lift_speed'] self.probe_offsets = probe.get_offsets() if self.horizontal_move_z < self.probe_offsets[2]: raise gcmd.error("horizontal_move_z can't be less than" @@ -501,8 +508,8 @@ def __init__(self, config, mcu_probe): self.cmd_helper = ProbeCommandHelper(config, self, mcu_probe.query_endstop) self.probe_session = ProbeSessionHelper(config, mcu_probe) - def get_lift_speed(self, gcmd=None): - return self.probe_session.get_lift_speed(gcmd) + def get_probe_params(self, gcmd=None): + return self.probe_session.get_probe_params(gcmd) def get_offsets(self): return self.probe_session.get_offsets() def get_status(self, eventtime): From 292512f8136ffabbc500612f1c80bdbe2588b8e1 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 23 May 2024 12:40:53 -0400 Subject: [PATCH 146/190] probe: Move PROBE_CALIBRATE to ProbeCommandHelper class Signed-off-by: Kevin O'Connor --- klippy/extras/probe.py | 66 ++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index c1ee40d99cee..c3b8a0dbf69a 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -34,10 +34,16 @@ def __init__(self, config, probe, query_endstop=None): self.last_z_result = 0. gcode.register_command('PROBE', self.cmd_PROBE, desc=self.cmd_PROBE_help) + # PROBE_CALIBRATE command + self.probe_calibrate_z = 0. + gcode.register_command('PROBE_CALIBRATE', self.cmd_PROBE_CALIBRATE, + desc=self.cmd_PROBE_CALIBRATE_help) # Other commands gcode.register_command('Z_OFFSET_APPLY_PROBE', self.cmd_Z_OFFSET_APPLY_PROBE, desc=self.cmd_Z_OFFSET_APPLY_PROBE_help) + def _move(self, coord, speed): + self.printer.lookup_object('toolhead').manual_move(coord, speed) def get_status(self, eventtime): return {'name': self.name, 'last_query': self.last_state, @@ -56,6 +62,35 @@ def cmd_PROBE(self, gcmd): pos = run_single_probe(self.probe, gcmd) gcmd.respond_info("Result is z=%.6f" % (pos[2],)) self.last_z_result = pos[2] + def probe_calibrate_finalize(self, kin_pos): + if kin_pos is None: + return + z_offset = self.probe_calibrate_z - kin_pos[2] + gcode = self.printer.lookup_object('gcode') + gcode.respond_info( + "%s: z_offset: %.3f\n" + "The SAVE_CONFIG command will update the printer config file\n" + "with the above and restart the printer." % (self.name, z_offset)) + configfile = self.printer.lookup_object('configfile') + configfile.set(self.name, 'z_offset', "%.3f" % (z_offset,)) + cmd_PROBE_CALIBRATE_help = "Calibrate the probe's z_offset" + def cmd_PROBE_CALIBRATE(self, gcmd): + manual_probe.verify_no_manual_probe(self.printer) + params = self.probe.get_probe_params(gcmd) + # Perform initial probe + curpos = run_single_probe(self.probe, gcmd) + # Move away from the bed + self.probe_calibrate_z = curpos[2] + curpos[2] += 5. + self._move(curpos, params['lift_speed']) + # Move the nozzle over the probe point + x_offset, y_offset, z_offset = self.probe.get_offsets() + curpos[0] += x_offset + curpos[1] += y_offset + self._move(curpos, params['probe_speed']) + # Start manual probe + manual_probe.ManualProbeHelper(self.printer, gcmd, + self.probe_calibrate_finalize) cmd_Z_OFFSET_APPLY_PROBE_help = "Adjust the probe's z_offset" def cmd_Z_OFFSET_APPLY_PROBE(self, gcmd): gcode_move = self.printer.lookup_object("gcode_move") @@ -77,14 +112,12 @@ def cmd_Z_OFFSET_APPLY_PROBE(self, gcmd): class ProbeSessionHelper: def __init__(self, config, mcu_probe): self.printer = config.get_printer() - self.name = config.get_name() self.mcu_probe = mcu_probe self.speed = config.getfloat('speed', 5.0, above=0.) self.lift_speed = config.getfloat('lift_speed', self.speed, above=0.) self.x_offset = config.getfloat('x_offset', 0.) self.y_offset = config.getfloat('y_offset', 0.) self.z_offset = config.getfloat('z_offset') - self.probe_calibrate_z = 0. self.multi_probe_pending = False # Infer Z position to move to during a probe if config.has_section('stepper_z'): @@ -121,8 +154,6 @@ def __init__(self, config, mcu_probe): self._handle_command_error) # Register PROBE/QUERY_PROBE commands self.gcode = self.printer.lookup_object('gcode') - self.gcode.register_command('PROBE_CALIBRATE', self.cmd_PROBE_CALIBRATE, - desc=self.cmd_PROBE_CALIBRATE_help) self.gcode.register_command('PROBE_ACCURACY', self.cmd_PROBE_ACCURACY, desc=self.cmd_PROBE_ACCURACY_help) self.dummy_gcode_cmd = self.gcode.create_gcode_command("", "", {}) @@ -290,33 +321,6 @@ def cmd_PROBE_ACCURACY(self, gcmd): "probe accuracy results: maximum %.6f, minimum %.6f, range %.6f, " "average %.6f, median %.6f, standard deviation %.6f" % ( max_value, min_value, range_value, avg_value, median, sigma)) - def probe_calibrate_finalize(self, kin_pos): - if kin_pos is None: - return - z_offset = self.probe_calibrate_z - kin_pos[2] - self.gcode.respond_info( - "%s: z_offset: %.3f\n" - "The SAVE_CONFIG command will update the printer config file\n" - "with the above and restart the printer." % (self.name, z_offset)) - configfile = self.printer.lookup_object('configfile') - configfile.set(self.name, 'z_offset', "%.3f" % (z_offset,)) - cmd_PROBE_CALIBRATE_help = "Calibrate the probe's z_offset" - def cmd_PROBE_CALIBRATE(self, gcmd): - manual_probe.verify_no_manual_probe(self.printer) - # Perform initial probe - params = self.get_probe_params(gcmd) - curpos = self.run_probe(gcmd) - # Move away from the bed - self.probe_calibrate_z = curpos[2] - curpos[2] += 5. - self._move(curpos, params['lift_speed']) - # Move the nozzle over the probe point - curpos[0] += self.x_offset - curpos[1] += self.y_offset - self._move(curpos, self.speed) - # Start manual probe - manual_probe.ManualProbeHelper(self.printer, gcmd, - self.probe_calibrate_finalize) ###################################################################### From f9a2920cee3b9311788bb5c53f1a9b4a3a01c9ee Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 23 May 2024 15:22:47 -0400 Subject: [PATCH 147/190] probe: Move PROBE_ACCURACY command to ProbeCommandHelper class Signed-off-by: Kevin O'Connor --- klippy/extras/probe.py | 140 ++++++++++++++++++++++------------------- 1 file changed, 74 insertions(+), 66 deletions(-) diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index c3b8a0dbf69a..fbafb0b7f68b 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -13,6 +13,22 @@ can travel further (the Z minimum position can be negative). """ +# Calculate the average Z from a set of positions +def calc_probe_z_average(positions, method='average'): + if method != 'median': + # Use mean average + count = float(len(positions)) + return [sum([pos[i] for pos in positions]) / count + for i in range(3)] + # Use median + z_sorted = sorted(positions, key=(lambda p: p[2])) + middle = len(positions) // 2 + if (len(positions) & 1) == 1: + # odd number of samples + return z_sorted[middle] + # even number of samples + return calc_probe_z_average(z_sorted[middle-1:middle+1], 'average') + ###################################################################### # Probe device implementation helpers @@ -39,6 +55,8 @@ def __init__(self, config, probe, query_endstop=None): gcode.register_command('PROBE_CALIBRATE', self.cmd_PROBE_CALIBRATE, desc=self.cmd_PROBE_CALIBRATE_help) # Other commands + gcode.register_command('PROBE_ACCURACY', self.cmd_PROBE_ACCURACY, + desc=self.cmd_PROBE_ACCURACY_help) gcode.register_command('Z_OFFSET_APPLY_PROBE', self.cmd_Z_OFFSET_APPLY_PROBE, desc=self.cmd_Z_OFFSET_APPLY_PROBE_help) @@ -91,6 +109,51 @@ def cmd_PROBE_CALIBRATE(self, gcmd): # Start manual probe manual_probe.ManualProbeHelper(self.printer, gcmd, self.probe_calibrate_finalize) + cmd_PROBE_ACCURACY_help = "Probe Z-height accuracy at current XY position" + def cmd_PROBE_ACCURACY(self, gcmd): + params = self.probe.get_probe_params(gcmd) + sample_count = gcmd.get_int("SAMPLES", 10, minval=1) + toolhead = self.printer.lookup_object('toolhead') + pos = toolhead.get_position() + gcmd.respond_info("PROBE_ACCURACY at X:%.3f Y:%.3f Z:%.3f" + " (samples=%d retract=%.3f" + " speed=%.1f lift_speed=%.1f)\n" + % (pos[0], pos[1], pos[2], + sample_count, params['sample_retract_dist'], + params['probe_speed'], params['lift_speed'])) + # Create dummy gcmd with SAMPLES=1 + fo_params = dict(gcmd.get_command_parameters()) + fo_params['SAMPLES'] = '1' + gcode = self.printer.lookup_object('gcode') + fo_gcmd = gcode.create_gcode_command("", "", fo_params) + # Probe bed sample_count times + probe_session = self.probe.start_probe_session(fo_gcmd) + probe_session.multi_probe_begin() + positions = [] + while len(positions) < sample_count: + # Probe position + pos = probe_session.run_probe(fo_gcmd) + positions.append(pos) + # Retract + liftpos = [None, None, pos[2] + params['sample_retract_dist']] + self._move(liftpos, params['lift_speed']) + probe_session.multi_probe_end() + # Calculate maximum, minimum and average values + max_value = max([p[2] for p in positions]) + min_value = min([p[2] for p in positions]) + range_value = max_value - min_value + avg_value = calc_probe_z_average(positions, 'average')[2] + median = calc_probe_z_average(positions, 'median')[2] + # calculate the standard deviation + deviation_sum = 0 + for i in range(len(positions)): + deviation_sum += pow(positions[i][2] - avg_value, 2.) + sigma = (deviation_sum / len(positions)) ** 0.5 + # Show information + gcmd.respond_info( + "probe accuracy results: maximum %.6f, minimum %.6f, range %.6f, " + "average %.6f, median %.6f, standard deviation %.6f" % ( + max_value, min_value, range_value, avg_value, median, sigma)) cmd_Z_OFFSET_APPLY_PROBE_help = "Adjust the probe's z_offset" def cmd_Z_OFFSET_APPLY_PROBE(self, gcmd): gcode_move = self.printer.lookup_object("gcode_move") @@ -119,6 +182,8 @@ def __init__(self, config, mcu_probe): self.y_offset = config.getfloat('y_offset', 0.) self.z_offset = config.getfloat('z_offset') self.multi_probe_pending = False + gcode = self.printer.lookup_object('gcode') + self.dummy_gcode_cmd = gcode.create_gcode_command("", "", {}) # Infer Z position to move to during a probe if config.has_section('stepper_z'): zconfig = config.getsection('stepper_z') @@ -152,11 +217,6 @@ def __init__(self, config, mcu_probe): self._handle_home_rails_end) self.printer.register_event_handler("gcode:command_error", self._handle_command_error) - # Register PROBE/QUERY_PROBE commands - self.gcode = self.printer.lookup_object('gcode') - self.gcode.register_command('PROBE_ACCURACY', self.cmd_PROBE_ACCURACY, - desc=self.cmd_PROBE_ACCURACY_help) - self.dummy_gcode_cmd = self.gcode.create_gcode_command("", "", {}) def _handle_homing_move_begin(self, hmove): if self.mcu_probe in hmove.get_mcu_endstops(): self.mcu_probe.probe_prepare(hmove) @@ -234,29 +294,17 @@ def _probe(self, speed): axis_twist_compensation.get_z_compensation_value(pos)) # add z compensation to probe position epos[2] += z_compensation - self.gcode.respond_info("probe at %.3f,%.3f is z=%.6f" - % (epos[0], epos[1], epos[2])) + gcode = self.printer.lookup_object('gcode') + gcode.respond_info("probe at %.3f,%.3f is z=%.6f" + % (epos[0], epos[1], epos[2])) return epos[:3] - def _move(self, coord, speed): - self.printer.lookup_object('toolhead').manual_move(coord, speed) - def _calc_mean(self, positions): - count = float(len(positions)) - return [sum([pos[i] for pos in positions]) / count - for i in range(3)] - def _calc_median(self, positions): - z_sorted = sorted(positions, key=(lambda p: p[2])) - middle = len(positions) // 2 - if (len(positions) & 1) == 1: - # odd number of samples - return z_sorted[middle] - # even number of samples - return self._calc_mean(z_sorted[middle-1:middle+1]) def run_probe(self, gcmd): params = self.get_probe_params(gcmd) must_notify_multi_probe = not self.multi_probe_pending if must_notify_multi_probe: self.multi_probe_begin() - probexy = self.printer.lookup_object('toolhead').get_position()[:2] + toolhead = self.printer.lookup_object('toolhead') + probexy = toolhead.get_position()[:2] retries = 0 positions = [] sample_count = params['samples'] @@ -274,53 +322,13 @@ def run_probe(self, gcmd): positions = [] # Retract if len(positions) < sample_count: - self._move(probexy + [pos[2] + params['sample_retract_dist']], - params['lift_speed']) + toolhead.manual_move( + probexy + [pos[2] + params['sample_retract_dist']], + params['lift_speed']) if must_notify_multi_probe: self.multi_probe_end() # Calculate and return result - if params['samples_result'] == 'median': - return self._calc_median(positions) - return self._calc_mean(positions) - cmd_PROBE_ACCURACY_help = "Probe Z-height accuracy at current XY position" - def cmd_PROBE_ACCURACY(self, gcmd): - params = self.get_probe_params(gcmd) - sample_count = gcmd.get_int("SAMPLES", 10, minval=1) - toolhead = self.printer.lookup_object('toolhead') - pos = toolhead.get_position() - gcmd.respond_info("PROBE_ACCURACY at X:%.3f Y:%.3f Z:%.3f" - " (samples=%d retract=%.3f" - " speed=%.1f lift_speed=%.1f)\n" - % (pos[0], pos[1], pos[2], - sample_count, params['sample_retract_dist'], - params['probe_speed'], params['lift_speed'])) - # Probe bed sample_count times - self.multi_probe_begin() - positions = [] - while len(positions) < sample_count: - # Probe position - pos = self._probe(params['probe_speed']) - positions.append(pos) - # Retract - liftpos = [None, None, pos[2] + params['sample_retract_dist']] - self._move(liftpos, params['lift_speed']) - self.multi_probe_end() - # Calculate maximum, minimum and average values - max_value = max([p[2] for p in positions]) - min_value = min([p[2] for p in positions]) - range_value = max_value - min_value - avg_value = self._calc_mean(positions)[2] - median = self._calc_median(positions)[2] - # calculate the standard deviation - deviation_sum = 0 - for i in range(len(positions)): - deviation_sum += pow(positions[i][2] - avg_value, 2.) - sigma = (deviation_sum / len(positions)) ** 0.5 - # Show information - gcmd.respond_info( - "probe accuracy results: maximum %.6f, minimum %.6f, range %.6f, " - "average %.6f, median %.6f, standard deviation %.6f" % ( - max_value, min_value, range_value, avg_value, median, sigma)) + return calc_probe_z_average(positions, params['samples_result']) ###################################################################### From de9798fb5be252627a47d73b89607243e201cab6 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 23 May 2024 15:48:15 -0400 Subject: [PATCH 148/190] probe: Move offset handling to new ProbeOffsetsHelper class Signed-off-by: Kevin O'Connor --- klippy/extras/probe.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index fbafb0b7f68b..0bf87460fa85 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -176,11 +176,6 @@ class ProbeSessionHelper: def __init__(self, config, mcu_probe): self.printer = config.get_printer() self.mcu_probe = mcu_probe - self.speed = config.getfloat('speed', 5.0, above=0.) - self.lift_speed = config.getfloat('lift_speed', self.speed, above=0.) - self.x_offset = config.getfloat('x_offset', 0.) - self.y_offset = config.getfloat('y_offset', 0.) - self.z_offset = config.getfloat('z_offset') self.multi_probe_pending = False gcode = self.printer.lookup_object('gcode') self.dummy_gcode_cmd = gcode.create_gcode_command("", "", {}) @@ -193,6 +188,9 @@ def __init__(self, config, mcu_probe): pconfig = config.getsection('printer') self.z_position = pconfig.getfloat('minimum_z_position', 0., note_valid=False) + # Configurable probing speeds + self.speed = config.getfloat('speed', 5.0, above=0.) + self.lift_speed = config.getfloat('lift_speed', self.speed, above=0.) # Multi-sample support (for improved accuracy) self.sample_count = config.getint('samples', 1, minval=1) self.sample_retract_dist = config.getfloat('sample_retract_dist', 2., @@ -269,8 +267,6 @@ def get_probe_params(self, gcmd=None): 'samples_tolerance': samples_tolerance, 'samples_tolerance_retries': samples_retries, 'samples_result': samples_result} - def get_offsets(self): - return self.x_offset, self.y_offset, self.z_offset def _probe(self, speed): toolhead = self.printer.lookup_object('toolhead') curtime = self.printer.get_reactor().monotonic() @@ -330,6 +326,15 @@ def run_probe(self, gcmd): # Calculate and return result return calc_probe_z_average(positions, params['samples_result']) +# Helper to read the xyz probe offsets from the config +class ProbeOffsetsHelper: + def __init__(self, config): + self.x_offset = config.getfloat('x_offset', 0.) + self.y_offset = config.getfloat('y_offset', 0.) + self.z_offset = config.getfloat('z_offset') + def get_offsets(self): + return self.x_offset, self.y_offset, self.z_offset + ###################################################################### # Tools for utilizing the probe @@ -519,11 +524,12 @@ def __init__(self, config, mcu_probe): self.printer = config.get_printer() self.cmd_helper = ProbeCommandHelper(config, self, mcu_probe.query_endstop) + self.probe_offsets = ProbeOffsetsHelper(config) self.probe_session = ProbeSessionHelper(config, mcu_probe) def get_probe_params(self, gcmd=None): return self.probe_session.get_probe_params(gcmd) def get_offsets(self): - return self.probe_session.get_offsets() + return self.probe_offsets.get_offsets() def get_status(self, eventtime): return self.cmd_helper.get_status(eventtime) def start_probe_session(self, gcmd): From 982a50c70ae6aa17538806de41137158ef682d58 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 23 May 2024 15:58:39 -0400 Subject: [PATCH 149/190] probe: Split z_virtual_endstop handling to new HomingViaProbeHelper class Signed-off-by: Kevin O'Connor --- klippy/extras/probe.py | 82 ++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index 0bf87460fa85..12e6109f6a96 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -171,6 +171,55 @@ def cmd_Z_OFFSET_APPLY_PROBE(self, gcmd): configfile = self.printer.lookup_object('configfile') configfile.set(self.name, 'z_offset', "%.3f" % (new_calibrate,)) +# Homing via probe:z_virtual_endstop +class HomingViaProbeHelper: + def __init__(self, config, mcu_probe): + self.printer = config.get_printer() + self.mcu_probe = mcu_probe + self.multi_probe_pending = False + # Register z_virtual_endstop pin + self.printer.lookup_object('pins').register_chip('probe', self) + # Register event handlers + self.printer.register_event_handler("homing:homing_move_begin", + self._handle_homing_move_begin) + self.printer.register_event_handler("homing:homing_move_end", + self._handle_homing_move_end) + self.printer.register_event_handler("homing:home_rails_begin", + self._handle_home_rails_begin) + self.printer.register_event_handler("homing:home_rails_end", + self._handle_home_rails_end) + self.printer.register_event_handler("gcode:command_error", + self._handle_command_error) + def _handle_homing_move_begin(self, hmove): + if self.mcu_probe in hmove.get_mcu_endstops(): + self.mcu_probe.probe_prepare(hmove) + def _handle_homing_move_end(self, hmove): + if self.mcu_probe in hmove.get_mcu_endstops(): + self.mcu_probe.probe_finish(hmove) + def _handle_home_rails_begin(self, homing_state, rails): + endstops = [es for rail in rails for es, name in rail.get_endstops()] + if self.mcu_probe in endstops: + self.mcu_probe.multi_probe_begin() + self.multi_probe_pending = True + def _handle_home_rails_end(self, homing_state, rails): + endstops = [es for rail in rails for es, name in rail.get_endstops()] + if self.multi_probe_pending and self.mcu_probe in endstops: + self.multi_probe_pending = False + self.mcu_probe.multi_probe_end() + def _handle_command_error(self): + if self.multi_probe_pending: + self.multi_probe_pending = False + try: + self.mcu_probe.multi_probe_end() + except: + logging.exception("Homing multi-probe end") + def setup_pin(self, pin_type, pin_params): + if pin_type != 'endstop' or pin_params['pin'] != 'z_virtual_endstop': + raise pins.error("Probe virtual endstop only useful as endstop pin") + if pin_params['invert'] or pin_params['pullup']: + raise pins.error("Can not pullup/invert probe virtual endstop") + return self.mcu_probe + # Helper to track multiple probe attempts in a single command class ProbeSessionHelper: def __init__(self, config, mcu_probe): @@ -188,6 +237,7 @@ def __init__(self, config, mcu_probe): pconfig = config.getsection('printer') self.z_position = pconfig.getfloat('minimum_z_position', 0., note_valid=False) + self.homing_helper = HomingViaProbeHelper(config, mcu_probe) # Configurable probing speeds self.speed = config.getfloat('speed', 5.0, above=0.) self.lift_speed = config.getfloat('lift_speed', self.speed, above=0.) @@ -202,33 +252,9 @@ def __init__(self, config, mcu_probe): minval=0.) self.samples_retries = config.getint('samples_tolerance_retries', 0, minval=0) - # Register z_virtual_endstop pin - self.printer.lookup_object('pins').register_chip('probe', self) - # Register homing event handlers - self.printer.register_event_handler("homing:homing_move_begin", - self._handle_homing_move_begin) - self.printer.register_event_handler("homing:homing_move_end", - self._handle_homing_move_end) - self.printer.register_event_handler("homing:home_rails_begin", - self._handle_home_rails_begin) - self.printer.register_event_handler("homing:home_rails_end", - self._handle_home_rails_end) + # Register event handlers self.printer.register_event_handler("gcode:command_error", self._handle_command_error) - def _handle_homing_move_begin(self, hmove): - if self.mcu_probe in hmove.get_mcu_endstops(): - self.mcu_probe.probe_prepare(hmove) - def _handle_homing_move_end(self, hmove): - if self.mcu_probe in hmove.get_mcu_endstops(): - self.mcu_probe.probe_finish(hmove) - def _handle_home_rails_begin(self, homing_state, rails): - endstops = [es for rail in rails for es, name in rail.get_endstops()] - if self.mcu_probe in endstops: - self.multi_probe_begin() - def _handle_home_rails_end(self, homing_state, rails): - endstops = [es for rail in rails for es, name in rail.get_endstops()] - if self.mcu_probe in endstops: - self.multi_probe_end() def _handle_command_error(self): try: self.multi_probe_end() @@ -241,12 +267,6 @@ def multi_probe_end(self): if self.multi_probe_pending: self.multi_probe_pending = False self.mcu_probe.multi_probe_end() - def setup_pin(self, pin_type, pin_params): - if pin_type != 'endstop' or pin_params['pin'] != 'z_virtual_endstop': - raise pins.error("Probe virtual endstop only useful as endstop pin") - if pin_params['invert'] or pin_params['pullup']: - raise pins.error("Can not pullup/invert probe virtual endstop") - return self.mcu_probe def get_probe_params(self, gcmd=None): if gcmd is None: gcmd = self.dummy_gcode_cmd From f4adb2999920e203445e0d6e12b0b08650ae5e19 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 23 May 2024 16:43:55 -0400 Subject: [PATCH 150/190] probe: Ensure all external callers always call end_probe_session() Rework ProbeSessionHelper's multi_probe_start() and multi_probe_end() to start_probe_session() and end_probe_session(). Ensure all external callers always invoke these methods prior to running run_probe(). Signed-off-by: Kevin O'Connor --- klippy/extras/probe.py | 47 +++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index 12e6109f6a96..ca778cc90e31 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -1,6 +1,6 @@ # Z-Probe support # -# Copyright (C) 2017-2021 Kevin O'Connor +# Copyright (C) 2017-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import logging @@ -128,7 +128,6 @@ def cmd_PROBE_ACCURACY(self, gcmd): fo_gcmd = gcode.create_gcode_command("", "", fo_params) # Probe bed sample_count times probe_session = self.probe.start_probe_session(fo_gcmd) - probe_session.multi_probe_begin() positions = [] while len(positions) < sample_count: # Probe position @@ -137,7 +136,7 @@ def cmd_PROBE_ACCURACY(self, gcmd): # Retract liftpos = [None, None, pos[2] + params['sample_retract_dist']] self._move(liftpos, params['lift_speed']) - probe_session.multi_probe_end() + probe_session.end_probe_session() # Calculate maximum, minimum and average values max_value = max([p[2] for p in positions]) min_value = min([p[2] for p in positions]) @@ -225,7 +224,6 @@ class ProbeSessionHelper: def __init__(self, config, mcu_probe): self.printer = config.get_printer() self.mcu_probe = mcu_probe - self.multi_probe_pending = False gcode = self.printer.lookup_object('gcode') self.dummy_gcode_cmd = gcode.create_gcode_command("", "", {}) # Infer Z position to move to during a probe @@ -252,21 +250,31 @@ def __init__(self, config, mcu_probe): minval=0.) self.samples_retries = config.getint('samples_tolerance_retries', 0, minval=0) + # Session state + self.multi_probe_pending = False # Register event handlers self.printer.register_event_handler("gcode:command_error", self._handle_command_error) def _handle_command_error(self): - try: - self.multi_probe_end() - except: - logging.exception("Multi-probe end") - def multi_probe_begin(self): + if self.multi_probe_pending: + try: + self.end_probe_session() + except: + logging.exception("Multi-probe end") + def _probe_state_error(self): + raise self.printer.command_error( + "Internal probe error - start/end probe session mismatch") + def start_probe_session(self, gcmd): + if self.multi_probe_pending: + self._probe_state_error() self.mcu_probe.multi_probe_begin() self.multi_probe_pending = True - def multi_probe_end(self): - if self.multi_probe_pending: - self.multi_probe_pending = False - self.mcu_probe.multi_probe_end() + return self + def end_probe_session(self): + if not self.multi_probe_pending: + self._probe_state_error() + self.multi_probe_pending = False + self.mcu_probe.multi_probe_end() def get_probe_params(self, gcmd=None): if gcmd is None: gcmd = self.dummy_gcode_cmd @@ -315,10 +323,9 @@ def _probe(self, speed): % (epos[0], epos[1], epos[2])) return epos[:3] def run_probe(self, gcmd): + if not self.multi_probe_pending: + self._probe_state_error() params = self.get_probe_params(gcmd) - must_notify_multi_probe = not self.multi_probe_pending - if must_notify_multi_probe: - self.multi_probe_begin() toolhead = self.printer.lookup_object('toolhead') probexy = toolhead.get_position()[:2] retries = 0 @@ -341,8 +348,6 @@ def run_probe(self, gcmd): toolhead.manual_move( probexy + [pos[2] + params['sample_retract_dist']], params['lift_speed']) - if must_notify_multi_probe: - self.multi_probe_end() # Calculate and return result return calc_probe_z_average(positions, params['samples_result']) @@ -436,14 +441,13 @@ def start_probe(self, gcmd): raise gcmd.error("horizontal_move_z can't be less than" " probe's z_offset") probe_session = probe.start_probe_session(gcmd) - probe_session.multi_probe_begin() while 1: done = self._move_next() if done: break pos = probe_session.run_probe(gcmd) self.results.append(pos) - probe_session.multi_probe_end() + probe_session.end_probe_session() def _manual_probe_start(self): done = self._move_next() if not done: @@ -460,6 +464,7 @@ def _manual_probe_finalize(self, kin_pos): def run_single_probe(probe, gcmd): probe_session = probe.start_probe_session(gcmd) pos = probe_session.run_probe(gcmd) + probe_session.end_probe_session() return pos @@ -553,7 +558,7 @@ def get_offsets(self): def get_status(self, eventtime): return self.cmd_helper.get_status(eventtime) def start_probe_session(self, gcmd): - return self.probe_session + return self.probe_session.start_probe_session(gcmd) def load_config(config): return PrinterProbe(config, ProbeEndstopWrapper(config)) From e780049a74310d23d110d8beac37b980459893a4 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 24 May 2024 21:04:49 -0400 Subject: [PATCH 151/190] probe: Use an event for axis twist compensation updates Instead of directly calling axis_twist_compensation, send an event that can perform the necessary updates. Signed-off-by: Kevin O'Connor --- klippy/extras/axis_twist_compensation.py | 9 ++++++--- klippy/extras/probe.py | 12 +++--------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/klippy/extras/axis_twist_compensation.py b/klippy/extras/axis_twist_compensation.py index e01951abb358..8f4a581a7708 100644 --- a/klippy/extras/axis_twist_compensation.py +++ b/klippy/extras/axis_twist_compensation.py @@ -38,10 +38,13 @@ def __init__(self, config): # setup calibrater self.calibrater = Calibrater(self, config) + # register events + self.printer.register_event_handler("probe:update_results", + self._update_z_compensation_value) - def get_z_compensation_value(self, pos): + def _update_z_compensation_value(self, pos): if not self.z_compensations: - return 0 + return x_coord = pos[0] z_compensations = self.z_compensations @@ -55,7 +58,7 @@ def get_z_compensation_value(self, pos): interpolated_z_compensation = BedMesh.lerp( interpolate_t, z_compensations[interpolate_i], z_compensations[interpolate_i + 1]) - return interpolated_z_compensation + pos[2] += interpolated_z_compensation def clear_compensations(self): self.z_compensations = [] diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index ca778cc90e31..b643dff9c91d 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -309,15 +309,9 @@ def _probe(self, speed): if "Timeout during endstop homing" in reason: reason += HINT_TIMEOUT raise self.printer.command_error(reason) - # get z compensation from axis_twist_compensation - axis_twist_compensation = self.printer.lookup_object( - 'axis_twist_compensation', None) - z_compensation = 0 - if axis_twist_compensation is not None: - z_compensation = ( - axis_twist_compensation.get_z_compensation_value(pos)) - # add z compensation to probe position - epos[2] += z_compensation + # Allow axis_twist_compensation to update results + self.printer.send_event("probe:update_results", epos) + # Report results gcode = self.printer.lookup_object('gcode') gcode.respond_info("probe at %.3f,%.3f is z=%.6f" % (epos[0], epos[1], epos[2])) From abfe3675d69f11567f7592f4b9659d2db46911a8 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 25 May 2024 20:46:56 -0400 Subject: [PATCH 152/190] bltouch: Use ppins.setup_pin() helper Signed-off-by: Kevin O'Connor --- klippy/extras/bltouch.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/klippy/extras/bltouch.py b/klippy/extras/bltouch.py index b01cdb9ea897..482578c7d38d 100644 --- a/klippy/extras/bltouch.py +++ b/klippy/extras/bltouch.py @@ -44,10 +44,7 @@ def __init__(self, config): self.next_cmd_time = self.action_end_time = 0. self.finish_home_complete = self.wait_trigger_complete = None # Create an "endstop" object to handle the sensor pin - pin = config.get('sensor_pin') - pin_params = ppins.lookup_pin(pin, can_invert=True, can_pullup=True) - mcu = pin_params['chip'] - self.mcu_endstop = mcu.setup_pin('endstop', pin_params) + self.mcu_endstop = ppins.setup_pin('endstop', config.get('sensor_pin')) # output mode omodes = {'5V': '5V', 'OD': 'OD', None: None} self.output_mode = config.getchoice('set_output_mode', omodes, None) From 58753e58a24678b78379d9fa124e691ffd624394 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 25 May 2024 20:47:40 -0400 Subject: [PATCH 153/190] probe: Use ppins.setup_pin() helper Signed-off-by: Kevin O'Connor --- klippy/extras/probe.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index b643dff9c91d..6f35f5e17a1a 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -480,10 +480,7 @@ def __init__(self, config): config, 'deactivate_gcode', '') # Create an "endstop" object to handle the probe pin ppins = self.printer.lookup_object('pins') - pin = config.get('pin') - pin_params = ppins.lookup_pin(pin, can_invert=True, can_pullup=True) - mcu = pin_params['chip'] - self.mcu_endstop = mcu.setup_pin('endstop', pin_params) + self.mcu_endstop = ppins.setup_pin('endstop', config.get('pin')) self.printer.register_event_handler('klippy:mcu_identify', self._handle_mcu_identify) # Wrappers From f72f94e299e33c77e6c1a4a75cf632fc8fc812e0 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 25 May 2024 20:52:18 -0400 Subject: [PATCH 154/190] probe: Move add_steppers() logic to HomingViaProbeHelper class Perform the initial add_steppers() configuration in a single location. Signed-off-by: Kevin O'Connor --- klippy/extras/bltouch.py | 7 ------- klippy/extras/probe.py | 14 +++++++------- klippy/extras/probe_eddy_current.py | 7 ------- 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/klippy/extras/bltouch.py b/klippy/extras/bltouch.py index 482578c7d38d..081b92714710 100644 --- a/klippy/extras/bltouch.py +++ b/klippy/extras/bltouch.py @@ -28,8 +28,6 @@ def __init__(self, config): self.printer = config.get_printer() self.printer.register_event_handler("klippy:connect", self.handle_connect) - self.printer.register_event_handler('klippy:mcu_identify', - self.handle_mcu_identify) self.position_endstop = config.getfloat('z_offset', minval=0.) self.stow_on_each_sample = config.getboolean('stow_on_each_sample', True) @@ -70,11 +68,6 @@ def __init__(self, config): desc=self.cmd_BLTOUCH_STORE_help) # multi probes state self.multi = 'OFF' - def handle_mcu_identify(self): - kin = self.printer.lookup_object('toolhead').get_kinematics() - for stepper in kin.get_steppers(): - if stepper.is_active_axis('z'): - self.add_stepper(stepper) def handle_connect(self): self.sync_mcu_print_time() self.next_cmd_time += 0.200 diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index 6f35f5e17a1a..bb09217ac18f 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -179,6 +179,8 @@ def __init__(self, config, mcu_probe): # Register z_virtual_endstop pin self.printer.lookup_object('pins').register_chip('probe', self) # Register event handlers + self.printer.register_event_handler('klippy:mcu_identify', + self._handle_mcu_identify) self.printer.register_event_handler("homing:homing_move_begin", self._handle_homing_move_begin) self.printer.register_event_handler("homing:homing_move_end", @@ -189,6 +191,11 @@ def __init__(self, config, mcu_probe): self._handle_home_rails_end) self.printer.register_event_handler("gcode:command_error", self._handle_command_error) + def _handle_mcu_identify(self): + kin = self.printer.lookup_object('toolhead').get_kinematics() + for stepper in kin.get_steppers(): + if stepper.is_active_axis('z'): + self.mcu_probe.add_stepper(stepper) def _handle_homing_move_begin(self, hmove): if self.mcu_probe in hmove.get_mcu_endstops(): self.mcu_probe.probe_prepare(hmove) @@ -481,8 +488,6 @@ def __init__(self, config): # Create an "endstop" object to handle the probe pin ppins = self.printer.lookup_object('pins') self.mcu_endstop = ppins.setup_pin('endstop', config.get('pin')) - self.printer.register_event_handler('klippy:mcu_identify', - self._handle_mcu_identify) # Wrappers self.get_mcu = self.mcu_endstop.get_mcu self.add_stepper = self.mcu_endstop.add_stepper @@ -492,11 +497,6 @@ def __init__(self, config): self.query_endstop = self.mcu_endstop.query_endstop # multi probes state self.multi = 'OFF' - def _handle_mcu_identify(self): - kin = self.printer.lookup_object('toolhead').get_kinematics() - for stepper in kin.get_steppers(): - if stepper.is_active_axis('z'): - self.add_stepper(stepper) def _raise_probe(self): toolhead = self.printer.lookup_object('toolhead') start_pos = toolhead.get_position() diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index 3f4a5e20612c..d2309617f081 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -196,13 +196,6 @@ def __init__(self, config, sensor_helper, calibration): self._samples = [] self._is_sampling = self._start_from_home = self._need_stop = False self._trigger_time = 0. - self._printer.register_event_handler('klippy:mcu_identify', - self._handle_mcu_identify) - def _handle_mcu_identify(self): - kin = self._printer.lookup_object('toolhead').get_kinematics() - for stepper in kin.get_steppers(): - if stepper.is_active_axis('z'): - self.add_stepper(stepper) # Measurement gathering def _start_measurements(self, is_home=False): self._need_stop = False From 068d2a9f5a70d87dd1158c22c8573e1823ec2fa1 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 27 May 2024 14:59:20 -0400 Subject: [PATCH 155/190] bltouch: No need to use PrinterProbe() class Directly register the BLTouchProbe() class as the main probe interface. Signed-off-by: Kevin O'Connor --- klippy/extras/bltouch.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/klippy/extras/bltouch.py b/klippy/extras/bltouch.py index 081b92714710..58f66819732d 100644 --- a/klippy/extras/bltouch.py +++ b/klippy/extras/bltouch.py @@ -1,6 +1,6 @@ # BLTouch support # -# Copyright (C) 2018-2021 Kevin O'Connor +# Copyright (C) 2018-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import logging @@ -23,11 +23,9 @@ } # BLTouch "endstop" wrapper -class BLTouchEndstopWrapper: +class BLTouchProbe: def __init__(self, config): self.printer = config.get_printer() - self.printer.register_event_handler("klippy:connect", - self.handle_connect) self.position_endstop = config.getfloat('z_offset', minval=0.) self.stow_on_each_sample = config.getboolean('stow_on_each_sample', True) @@ -60,14 +58,30 @@ def __init__(self, config): self.get_steppers = self.mcu_endstop.get_steppers self.home_wait = self.mcu_endstop.home_wait self.query_endstop = self.mcu_endstop.query_endstop + # multi probes state + self.multi = 'OFF' + # Common probe implementation helpers + self.cmd_helper = probe.ProbeCommandHelper( + config, self, self.mcu_endstop.query_endstop) + self.probe_offsets = probe.ProbeOffsetsHelper(config) + self.probe_session = probe.ProbeSessionHelper(config, self) # Register BLTOUCH_DEBUG command self.gcode = self.printer.lookup_object('gcode') self.gcode.register_command("BLTOUCH_DEBUG", self.cmd_BLTOUCH_DEBUG, desc=self.cmd_BLTOUCH_DEBUG_help) self.gcode.register_command("BLTOUCH_STORE", self.cmd_BLTOUCH_STORE, desc=self.cmd_BLTOUCH_STORE_help) - # multi probes state - self.multi = 'OFF' + # Register events + self.printer.register_event_handler("klippy:connect", + self.handle_connect) + def get_probe_params(self, gcmd=None): + return self.probe_session.get_probe_params(gcmd) + def get_offsets(self): + return self.probe_offsets.get_offsets() + def get_status(self, eventtime): + return self.cmd_helper.get_status(eventtime) + def start_probe_session(self, gcmd): + return self.probe_session.start_probe_session(gcmd) def handle_connect(self): self.sync_mcu_print_time() self.next_cmd_time += 0.200 @@ -268,6 +282,6 @@ def cmd_BLTOUCH_STORE(self, gcmd): self.sync_print_time() def load_config(config): - blt = BLTouchEndstopWrapper(config) - config.get_printer().add_object('probe', probe.PrinterProbe(config, blt)) + blt = BLTouchProbe(config) + config.get_printer().add_object('probe', blt) return blt From 93245b3678b4bf64b5aff8322d5d5b3de38d00d2 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 27 May 2024 15:06:59 -0400 Subject: [PATCH 156/190] smart_effector: No need to use PrinterProbe() class Directly register the SmartEffectorProbe() class as the main probe interface. Signed-off-by: Kevin O'Connor --- klippy/extras/smart_effector.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/klippy/extras/smart_effector.py b/klippy/extras/smart_effector.py index c33de5275fc2..726531421ac9 100644 --- a/klippy/extras/smart_effector.py +++ b/klippy/extras/smart_effector.py @@ -48,7 +48,7 @@ def write_bits(self, start_time, bit_stream): bit_time += bit_step return bit_time -class SmartEffectorEndstopWrapper: +class SmartEffectorProbe: def __init__(self, config): self.printer = config.get_printer() self.gcode = self.printer.lookup_object('gcode') @@ -64,6 +64,11 @@ def __init__(self, config): self.query_endstop = self.probe_wrapper.query_endstop self.multi_probe_begin = self.probe_wrapper.multi_probe_begin self.multi_probe_end = self.probe_wrapper.multi_probe_end + # Common probe implementation helpers + self.cmd_helper = probe.ProbeCommandHelper( + config, self, self.probe_wrapper.query_endstop) + self.probe_offsets = probe.ProbeOffsetsHelper(config) + self.probe_session = probe.ProbeSessionHelper(config, self) # SmartEffector control control_pin = config.get('control_pin', None) if control_pin: @@ -78,6 +83,14 @@ def __init__(self, config): self.gcode.register_command("SET_SMART_EFFECTOR", self.cmd_SET_SMART_EFFECTOR, desc=self.cmd_SET_SMART_EFFECTOR_help) + def get_probe_params(self, gcmd=None): + return self.probe_session.get_probe_params(gcmd) + def get_offsets(self): + return self.probe_offsets.get_offsets() + def get_status(self, eventtime): + return self.cmd_helper.get_status(eventtime) + def start_probe_session(self, gcmd): + return self.probe_session.start_probe_session(gcmd) def probing_move(self, pos, speed): phoming = self.printer.lookup_object('homing') return phoming.probing_move(self, pos, speed) @@ -151,7 +164,6 @@ def cmd_RESET_SMART_EFFECTOR(self, gcmd): gcmd.respond_info('SmartEffector sensitivity was reset') def load_config(config): - smart_effector = SmartEffectorEndstopWrapper(config) - config.get_printer().add_object('probe', - probe.PrinterProbe(config, smart_effector)) + smart_effector = SmartEffectorProbe(config) + config.get_printer().add_object('probe', smart_effector) return smart_effector From 931d1ce8f42ed112e6655c9524367941efb86595 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 27 May 2024 15:13:48 -0400 Subject: [PATCH 157/190] probe_eddy_current: No need to use PrinterProbe() class Directly register the PrinterEddyProbe() class as the main probe interface. Signed-off-by: Kevin O'Connor --- klippy/extras/probe_eddy_current.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index d2309617f081..2fbde3960874 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -318,11 +318,23 @@ def __init__(self, config): sensor_type = config.getchoice('sensor_type', {s: s for s in sensors}) self.sensor_helper = sensors[sensor_type](config, self.calibration) # Probe interface - self.probe = EddyEndstopWrapper(config, self.sensor_helper, - self.calibration) - self.printer.add_object('probe', probe.PrinterProbe(config, self.probe)) + self.mcu_probe = EddyEndstopWrapper(config, self.sensor_helper, + self.calibration) + self.cmd_helper = probe.ProbeCommandHelper( + config, self, self.mcu_probe.query_endstop) + self.probe_offsets = probe.ProbeOffsetsHelper(config) + self.probe_session = probe.ProbeSessionHelper(config, self.mcu_probe) + self.printer.add_object('probe', self) def add_client(self, cb): self.sensor_helper.add_client(cb) + def get_probe_params(self, gcmd=None): + return self.probe_session.get_probe_params(gcmd) + def get_offsets(self): + return self.probe_offsets.get_offsets() + def get_status(self, eventtime): + return self.cmd_helper.get_status(eventtime) + def start_probe_session(self, gcmd): + return self.probe_session.start_probe_session(gcmd) def load_config_prefix(config): return PrinterEddyProbe(config) From d4bae4dffe8d3996ed1b1dd8f5230a062ae3d033 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 27 May 2024 15:43:46 -0400 Subject: [PATCH 158/190] probe: Simplify PrinterProbe() now that there are no external callers Create the mcu_probe interface locally within PrinterProbe(). Signed-off-by: Kevin O'Connor --- klippy/extras/probe.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index bb09217ac18f..5a976a06c150 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -536,12 +536,13 @@ def get_position_endstop(self): # Main external probe interface class PrinterProbe: - def __init__(self, config, mcu_probe): + def __init__(self, config): self.printer = config.get_printer() + self.mcu_probe = ProbeEndstopWrapper(config) self.cmd_helper = ProbeCommandHelper(config, self, - mcu_probe.query_endstop) + self.mcu_probe.query_endstop) self.probe_offsets = ProbeOffsetsHelper(config) - self.probe_session = ProbeSessionHelper(config, mcu_probe) + self.probe_session = ProbeSessionHelper(config, self.mcu_probe) def get_probe_params(self, gcmd=None): return self.probe_session.get_probe_params(gcmd) def get_offsets(self): @@ -552,4 +553,4 @@ def start_probe_session(self, gcmd): return self.probe_session.start_probe_session(gcmd) def load_config(config): - return PrinterProbe(config, ProbeEndstopWrapper(config)) + return PrinterProbe(config) From 17c645f000da6fe99bf75fd79f8e622747b1709e Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 5 Jun 2024 20:01:50 -0400 Subject: [PATCH 159/190] msgproto: Support multi-byte command and response ids Update the msgproto.py code so that it can support message ids that are larger than a single byte. (The host C code in klippy/chelper/msgblock.c already supports multi-byte ids.) Signed-off-by: Kevin O'Connor --- klippy/mcu.py | 2 +- klippy/msgproto.py | 61 +++++++++++++++++++++++----------------------- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/klippy/mcu.py b/klippy/mcu.py index d7a679acb114..6b106245bdcd 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -87,7 +87,7 @@ def __init__(self, serial, msgformat, cmd_queue=None): if cmd_queue is None: cmd_queue = serial.get_default_command_queue() self._cmd_queue = cmd_queue - self._msgtag = msgparser.lookup_msgtag(msgformat) & 0xffffffff + self._msgtag = msgparser.lookup_msgid(msgformat) & 0xffffffff def send(self, data=(), minclock=0, reqclock=0): cmd = self._cmd.encode(data) self._serial.raw_send(cmd, minclock, reqclock, self._cmd_queue) diff --git a/klippy/msgproto.py b/klippy/msgproto.py index f8a12530e67e..0fe765934590 100644 --- a/klippy/msgproto.py +++ b/klippy/msgproto.py @@ -1,6 +1,6 @@ # Protocol definitions for firmware communication # -# Copyright (C) 2016-2021 Kevin O'Connor +# Copyright (C) 2016-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import json, zlib, logging @@ -160,8 +160,8 @@ def convert_msg_format(msgformat): return msgformat class MessageFormat: - def __init__(self, msgid, msgformat, enumerations={}): - self.msgid = msgid + def __init__(self, msgid_bytes, msgformat, enumerations={}): + self.msgid_bytes = msgid_bytes self.msgformat = msgformat self.debugformat = convert_msg_format(msgformat) self.name = msgformat.split()[0] @@ -169,19 +169,17 @@ def __init__(self, msgid, msgformat, enumerations={}): self.param_types = [t for name, t in self.param_names] self.name_to_type = dict(self.param_names) def encode(self, params): - out = [] - out.append(self.msgid) + out = list(self.msgid_bytes) for i, t in enumerate(self.param_types): t.encode(out, params[i]) return out def encode_by_name(self, **params): - out = [] - out.append(self.msgid) + out = list(self.msgid_bytes) for name, t in self.param_names: t.encode(out, params[name]) return out def parse(self, s, pos): - pos += 1 + pos += len(self.msgid_bytes) out = {} for name, t in self.param_names: v, pos = t.parse(s, pos) @@ -198,13 +196,13 @@ def format_params(self, params): class OutputFormat: name = '#output' - def __init__(self, msgid, msgformat): - self.msgid = msgid + def __init__(self, msgid_bytes, msgformat): + self.msgid_bytes = msgid_bytes self.msgformat = msgformat self.debugformat = convert_msg_format(msgformat) self.param_types = lookup_output_params(msgformat) def parse(self, s, pos): - pos += 1 + pos += len(self.msgid_bytes) out = [] for t in self.param_types: v, pos = t.parse(s, pos) @@ -219,7 +217,7 @@ def format_params(self, params): class UnknownFormat: name = '#unknown' def parse(self, s, pos): - msgid = s[pos] + msgid, param_pos = PT_int32().parse(s, pos) msg = bytes(bytearray(s)) return {'#msgid': msgid, '#msg': msg}, len(s)-MESSAGE_TRAILER_SIZE def format_params(self, params): @@ -234,7 +232,8 @@ def __init__(self, warn_prefix=""): self.messages = [] self.messages_by_id = {} self.messages_by_name = {} - self.msgtag_by_format = {} + self.msgid_by_format = {} + self.msgid_parser = PT_int32() self.config = {} self.version = self.build_versions = "" self.raw_identify_data = "" @@ -266,7 +265,7 @@ def dump(self, s): out = ["seq: %02x" % (msgseq,)] pos = MESSAGE_HEADER_SIZE while 1: - msgid = s[pos] + msgid, param_pos = self.msgid_parser.parse(s, pos) mid = self.messages_by_id.get(msgid, self.unknown) params, pos = mid.parse(s, pos) out.append(mid.format_params(params)) @@ -283,14 +282,14 @@ def format_params(self, params): return "%s %s" % (name, msg) return str(params) def parse(self, s): - msgid = s[MESSAGE_HEADER_SIZE] + msgid, param_pos = self.msgid_parser.parse(s, MESSAGE_HEADER_SIZE) mid = self.messages_by_id.get(msgid, self.unknown) params, pos = mid.parse(s, MESSAGE_HEADER_SIZE) if pos != len(s)-MESSAGE_TRAILER_SIZE: self._error("Extra data at end of message") params['#name'] = mid.name return params - def encode(self, seq, cmd): + def encode_msgblock(self, seq, cmd): msglen = MESSAGE_MIN + len(cmd) seq = (seq & MESSAGE_SEQ_MASK) | MESSAGE_DEST out = [msglen, seq] + cmd @@ -317,11 +316,11 @@ def lookup_command(self, msgformat): self._error("Command format mismatch: %s vs %s", msgformat, mp.msgformat) return mp - def lookup_msgtag(self, msgformat): - msgtag = self.msgtag_by_format.get(msgformat) - if msgtag is None: + def lookup_msgid(self, msgformat): + msgid = self.msgid_by_format.get(msgformat) + if msgid is None: self._error("Unknown command: %s", msgformat) - return msgtag + return msgid def create_command(self, msg): parts = msg.strip().split() if not parts: @@ -372,22 +371,22 @@ def fill_enumerations(self, enumerations): start_value, count = value for i in range(count): enums[enum_root + str(start_enum + i)] = start_value + i - def _init_messages(self, messages, command_tags=[], output_tags=[]): - for msgformat, msgtag in messages.items(): + def _init_messages(self, messages, command_ids=[], output_ids=[]): + for msgformat, msgid in messages.items(): msgtype = 'response' - if msgtag in command_tags: + if msgid in command_ids: msgtype = 'command' - elif msgtag in output_tags: + elif msgid in output_ids: msgtype = 'output' - self.messages.append((msgtag, msgtype, msgformat)) - if msgtag < -32 or msgtag > 95: - self._error("Multi-byte msgtag not supported") - self.msgtag_by_format[msgformat] = msgtag - msgid = msgtag & 0x7f + self.messages.append((msgid, msgtype, msgformat)) + self.msgid_by_format[msgformat] = msgid + msgid_bytes = [] + self.msgid_parser.encode(msgid_bytes, msgid) if msgtype == 'output': - self.messages_by_id[msgid] = OutputFormat(msgid, msgformat) + self.messages_by_id[msgid] = OutputFormat(msgid_bytes, + msgformat) else: - msg = MessageFormat(msgid, msgformat, self.enumerations) + msg = MessageFormat(msgid_bytes, msgformat, self.enumerations) self.messages_by_id[msgid] = msg self.messages_by_name[msg.name] = msg def process_identify(self, data, decompress=True): From 36b8831c7e7c4e4481704234fbc27dbe43026f73 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 5 Jun 2024 20:20:24 -0400 Subject: [PATCH 160/190] sensor_bulk: Change maximum data size from 52 to 51 bytes Reduce the maximum data size from 52 bytes to 51 bytes. This will enable support for 2-byte response ids. This change would alter the behavior of the ldc1612 sensor support. Force an ldc1612 command name change so that users are alerted that they must rebuild the micro-controller code upon update of the host code. Signed-off-by: Kevin O'Connor --- klippy/extras/bulk_sensor.py | 2 +- klippy/extras/ldc1612.py | 2 +- src/sensor_bulk.h | 2 +- src/sensor_ldc1612.c | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/klippy/extras/bulk_sensor.py b/klippy/extras/bulk_sensor.py index 1720c0522736..b0aa320d085a 100644 --- a/klippy/extras/bulk_sensor.py +++ b/klippy/extras/bulk_sensor.py @@ -198,7 +198,7 @@ def get_time_translation(self): inv_freq = clock_to_print_time(base_mcu + inv_cfreq) - base_time return base_time, base_chip, inv_freq -MAX_BULK_MSG_SIZE = 52 +MAX_BULK_MSG_SIZE = 51 # Read sensor_bulk_data and calculate timestamps for devices that take # samples at a fixed frequency (and produce fixed data size samples). diff --git a/klippy/extras/ldc1612.py b/klippy/extras/ldc1612.py index cdc01e04505e..281d34226997 100644 --- a/klippy/extras/ldc1612.py +++ b/klippy/extras/ldc1612.py @@ -117,7 +117,7 @@ def _build_config(self): cmdqueue = self.i2c.get_command_queue() self.query_ldc1612_cmd = self.mcu.lookup_command( "query_ldc1612 oid=%c rest_ticks=%u", cq=cmdqueue) - self.ffreader.setup_query_command("query_ldc1612_status oid=%c", + self.ffreader.setup_query_command("query_status_ldc1612 oid=%c", oid=self.oid, cq=cmdqueue) self.ldc1612_setup_home_cmd = self.mcu.lookup_command( "ldc1612_setup_home oid=%c clock=%u threshold=%u" diff --git a/src/sensor_bulk.h b/src/sensor_bulk.h index 9c130bea3c64..c750dbdaeded 100644 --- a/src/sensor_bulk.h +++ b/src/sensor_bulk.h @@ -4,7 +4,7 @@ struct sensor_bulk { uint16_t sequence, possible_overflows; uint8_t data_count; - uint8_t data[52]; + uint8_t data[51]; }; void sensor_bulk_reset(struct sensor_bulk *sb); diff --git a/src/sensor_ldc1612.c b/src/sensor_ldc1612.c index 6e52c177c07e..01cf3ee04597 100644 --- a/src/sensor_ldc1612.c +++ b/src/sensor_ldc1612.c @@ -210,7 +210,7 @@ command_query_ldc1612(uint32_t *args) DECL_COMMAND(command_query_ldc1612, "query_ldc1612 oid=%c rest_ticks=%u"); void -command_query_ldc1612_status(uint32_t *args) +command_query_status_ldc1612(uint32_t *args) { struct ldc1612 *ld = oid_lookup(args[0], command_config_ldc1612); @@ -232,7 +232,7 @@ command_query_ldc1612_status(uint32_t *args) uint32_t fifo = status & 0x08 ? BYTES_PER_SAMPLE : 0; sensor_bulk_status(&ld->sb, args[0], time1, time2-time1, fifo); } -DECL_COMMAND(command_query_ldc1612_status, "query_ldc1612_status oid=%c"); +DECL_COMMAND(command_query_status_ldc1612, "query_status_ldc1612 oid=%c"); void ldc1612_task(void) From 589bd64ce013691ef8989d3dfbc74ffe0822d480 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 5 Jun 2024 21:37:57 -0400 Subject: [PATCH 161/190] command: Support 2-byte message ids Allow command ids, response ids, and output ids to be either 1 or 2 bytes long. This increases the total number of message types from 128 to 16384. Signed-off-by: Kevin O'Connor --- scripts/buildcommands.py | 98 +++++++++++++++++++++++----------------- src/command.c | 30 ++++++++++-- src/command.h | 9 ++-- src/pru/pru0.c | 2 +- 4 files changed, 90 insertions(+), 49 deletions(-) diff --git a/scripts/buildcommands.py b/scripts/buildcommands.py index 236373c2f078..b35873840add 100644 --- a/scripts/buildcommands.py +++ b/scripts/buildcommands.py @@ -251,8 +251,9 @@ class HandleCommandGeneration: def __init__(self): self.commands = {} self.encoders = [] - self.msg_to_id = dict(msgproto.DefaultMessages) - self.messages_by_name = { m.split()[0]: m for m in self.msg_to_id } + self.msg_to_encid = dict(msgproto.DefaultMessages) + self.encid_to_msgid = {} + self.messages_by_name = { m.split()[0]: m for m in self.msg_to_encid } self.all_param_types = {} self.ctr_dispatch = { 'DECL_COMMAND_FLAGS': self.decl_command, @@ -280,37 +281,47 @@ def decl_encoder(self, req): def decl_output(self, req): msg = req.split(None, 1)[1] self.encoders.append((None, msg)) + def convert_encoded_msgid(self, encoded_msgid): + if encoded_msgid >= 0x80: + data = [(encoded_msgid >> 7) | 0x80, encoded_msgid & 0x7f] + else: + data = [encoded_msgid] + return msgproto.PT_int32().parse(data, 0)[0] def create_message_ids(self): # Create unique ids for each message type - msgid = max(self.msg_to_id.values()) + encoded_msgid = max(self.msg_to_encid.values()) mlist = list(self.commands.keys()) + [m for n, m in self.encoders] for msgname in mlist: msg = self.messages_by_name.get(msgname, msgname) - if msg not in self.msg_to_id: - msgid += 1 - self.msg_to_id[msg] = msgid - if msgid >= 128: - # The mcu currently assumes all message ids encode to one byte + if msg not in self.msg_to_encid: + encoded_msgid += 1 + self.msg_to_encid[msg] = encoded_msgid + if encoded_msgid >= 1<<14: + # The mcu currently assumes all message ids encode to 1 or 2 bytes error("Too many message ids") + self.encid_to_msgid = { + encoded_msgid: self.convert_encoded_msgid(encoded_msgid) + for encoded_msgid in self.msg_to_encid.values() + } def update_data_dictionary(self, data): - # Handle message ids over 96 (they are decoded as negative numbers) - msg_to_tag = {msg: msgid if msgid < 96 else msgid - 128 - for msg, msgid in self.msg_to_id.items()} - command_tags = [msg_to_tag[msg] + # Convert ids to standard form (use both positive and negative numbers) + msg_to_msgid = {msg: self.encid_to_msgid[encoded_msgid] + for msg, encoded_msgid in self.msg_to_encid.items()} + command_ids = [msg_to_msgid[msg] + for msgname, msg in self.messages_by_name.items() + if msgname in self.commands] + response_ids = [msg_to_msgid[msg] for msgname, msg in self.messages_by_name.items() - if msgname in self.commands] - response_tags = [msg_to_tag[msg] - for msgname, msg in self.messages_by_name.items() - if msgname not in self.commands] - data['commands'] = { msg: msgtag for msg, msgtag in msg_to_tag.items() - if msgtag in command_tags } - data['responses'] = { msg: msgtag for msg, msgtag in msg_to_tag.items() - if msgtag in response_tags } - output = {msg: msgtag for msg, msgtag in msg_to_tag.items() - if msgtag not in command_tags and msgtag not in response_tags} + if msgname not in self.commands] + data['commands'] = { msg: msgid for msg, msgid in msg_to_msgid.items() + if msgid in command_ids } + data['responses'] = { msg: msgid for msg, msgid in msg_to_msgid.items() + if msgid in response_ids } + output = {msg: msgid for msg, msgid in msg_to_msgid.items() + if msgid not in command_ids and msgid not in response_ids} if output: data['output'] = output - def build_parser(self, msgid, msgformat, msgtype): + def build_parser(self, encoded_msgid, msgformat, msgtype): if msgtype == "output": param_types = msgproto.lookup_output_params(msgformat) comment = "Output: " + msgformat @@ -327,17 +338,21 @@ def build_parser(self, msgid, msgformat, msgtype): params = 'command_parameters%d' % (paramid,) out = """ // %s - .msg_id=%d, + .encoded_msgid=%d, // msgid=%d .num_params=%d, .param_types = %s, -""" % (comment, msgid, len(types), params) +""" % (comment, encoded_msgid, self.encid_to_msgid[encoded_msgid], + len(types), params) if msgtype == 'response': num_args = (len(types) + types.count('PT_progmem_buffer') + types.count('PT_buffer')) out += " .num_args=%d," % (num_args,) else: + msgid_size = 1 + if encoded_msgid >= 0x80: + msgid_size = 2 max_size = min(msgproto.MESSAGE_MAX, - (msgproto.MESSAGE_MIN + 1 + (msgproto.MESSAGE_MIN + msgid_size + sum([t.max_length for t in param_types]))) out += " .max_size=%d," % (max_size,) return out @@ -347,22 +362,23 @@ def generate_responses_code(self): encoder_code = [] did_output = {} for msgname, msg in self.encoders: - msgid = self.msg_to_id[msg] - if msgid in did_output: + encoded_msgid = self.msg_to_encid[msg] + if encoded_msgid in did_output: continue - did_output[msgid] = True + did_output[encoded_msgid] = True code = (' if (__builtin_strcmp(str, "%s") == 0)\n' - ' return &command_encoder_%s;\n' % (msg, msgid)) + ' return &command_encoder_%s;\n' + % (msg, encoded_msgid)) if msgname is None: - parsercode = self.build_parser(msgid, msg, 'output') + parsercode = self.build_parser(encoded_msgid, msg, 'output') output_code.append(code) else: - parsercode = self.build_parser(msgid, msg, 'command') + parsercode = self.build_parser(encoded_msgid, msg, 'command') encoder_code.append(code) encoder_defs.append( "const struct command_encoder command_encoder_%s PROGMEM = {" " %s\n};\n" % ( - msgid, parsercode)) + encoded_msgid, parsercode)) fmt = """ %s @@ -384,21 +400,21 @@ def generate_responses_code(self): "".join(encoder_code).strip(), "".join(output_code).strip()) def generate_commands_code(self): - cmd_by_id = { - self.msg_to_id[self.messages_by_name.get(msgname, msgname)]: cmd + cmd_by_encid = { + self.msg_to_encid[self.messages_by_name.get(msgname, msgname)]: cmd for msgname, cmd in self.commands.items() } - max_cmd_msgid = max(cmd_by_id.keys()) + max_cmd_encid = max(cmd_by_encid.keys()) index = [] externs = {} - for msgid in range(max_cmd_msgid+1): - if msgid not in cmd_by_id: + for encoded_msgid in range(max_cmd_encid+1): + if encoded_msgid not in cmd_by_encid: index.append(" {\n},") continue - funcname, flags, msgname = cmd_by_id[msgid] + funcname, flags, msgname = cmd_by_encid[encoded_msgid] msg = self.messages_by_name[msgname] externs[funcname] = 1 - parsercode = self.build_parser(msgid, msg, 'response') + parsercode = self.build_parser(encoded_msgid, msg, 'response') index.append(" {%s\n .flags=%s,\n .func=%s\n}," % ( parsercode, flags, funcname)) index = "".join(index).strip() @@ -411,7 +427,7 @@ def generate_commands_code(self): %s }; -const uint8_t command_index_size PROGMEM = ARRAY_SIZE(command_index); +const uint16_t command_index_size PROGMEM = ARRAY_SIZE(command_index); """ return fmt % (externs, index) def generate_param_code(self): diff --git a/src/command.c b/src/command.c index 39c09458b0f5..d2d05aff9378 100644 --- a/src/command.c +++ b/src/command.c @@ -1,6 +1,6 @@ // Code for parsing incoming commands and encoding outgoing messages // -// Copyright (C) 2016,2017 Kevin O'Connor +// Copyright (C) 2016-2024 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -69,6 +69,28 @@ parse_int(uint8_t **pp) return v; } +// Write an encoded msgid (optimized 2-byte VLQ encoder) +static uint8_t * +encode_msgid(uint8_t *p, uint_fast16_t encoded_msgid) +{ + if (encoded_msgid >= 0x80) + *p++ = (encoded_msgid >> 7) | 0x80; + *p++ = encoded_msgid & 0x7f; + return p; +} + +// Parse an encoded msgid (optimized 2-byte parser, return as positive number) +uint_fast16_t +command_parse_msgid(uint8_t **pp) +{ + uint8_t *p = *pp; + uint_fast16_t encoded_msgid = *p++; + if (encoded_msgid & 0x80) + encoded_msgid = ((encoded_msgid & 0x7f) << 7) | (*p++); + *pp = p; + return encoded_msgid; +} + // Parse an incoming command into 'args' uint8_t * command_parsef(uint8_t *p, uint8_t *maxend @@ -119,7 +141,7 @@ command_encodef(uint8_t *buf, const struct command_encoder *ce, va_list args) uint8_t *maxend = &p[max_size - MESSAGE_MIN]; uint_fast8_t num_params = READP(ce->num_params); const uint8_t *param_types = READP(ce->param_types); - *p++ = READP(ce->msg_id); + p = encode_msgid(p, READP(ce->encoded_msgid)); while (num_params--) { if (p > maxend) goto error; @@ -227,7 +249,7 @@ DECL_SHUTDOWN(sendf_shutdown); // Find the command handler associated with a command static const struct command_parser * -command_lookup_parser(uint_fast8_t cmdid) +command_lookup_parser(uint_fast16_t cmdid) { if (!cmdid || cmdid >= READP(command_index_size)) shutdown("Invalid command"); @@ -309,7 +331,7 @@ command_dispatch(uint8_t *buf, uint_fast8_t msglen) uint8_t *p = &buf[MESSAGE_HEADER_SIZE]; uint8_t *msgend = &buf[msglen-MESSAGE_TRAILER_SIZE]; while (p < msgend) { - uint_fast8_t cmdid = *p++; + uint_fast16_t cmdid = command_parse_msgid(&p); const struct command_parser *cp = command_lookup_parser(cmdid); uint32_t args[READP(cp->num_args)]; p = command_parsef(p, msgend, cp, args); diff --git a/src/command.h b/src/command.h index 894114d71ed6..21b3f79b8ecf 100644 --- a/src/command.h +++ b/src/command.h @@ -57,11 +57,13 @@ #define MESSAGE_SYNC 0x7E struct command_encoder { - uint8_t msg_id, max_size, num_params; + uint16_t encoded_msgid; + uint8_t max_size, num_params; const uint8_t *param_types; }; struct command_parser { - uint8_t msg_id, num_args, flags, num_params; + uint16_t encoded_msgid; + uint8_t num_args, flags, num_params; const uint8_t *param_types; void (*func)(uint32_t *args); }; @@ -72,6 +74,7 @@ enum { // command.c void *command_decode_ptr(uint32_t v); +uint_fast16_t command_parse_msgid(uint8_t **pp); uint8_t *command_parsef(uint8_t *p, uint8_t *maxend , const struct command_parser *cp, uint32_t *args); uint_fast8_t command_encode_and_frame( @@ -86,7 +89,7 @@ int_fast8_t command_find_and_dispatch(uint8_t *buf, uint_fast8_t buf_len // out/compile_time_request.c (auto generated file) extern const struct command_parser command_index[]; -extern const uint8_t command_index_size; +extern const uint16_t command_index_size; extern const uint8_t command_identify_data[]; extern const uint32_t command_identify_size; const struct command_encoder *ctr_lookup_encoder(const char *str); diff --git a/src/pru/pru0.c b/src/pru/pru0.c index 57d55d2792f7..8a11e14027a8 100644 --- a/src/pru/pru0.c +++ b/src/pru/pru0.c @@ -141,7 +141,7 @@ do_dispatch(uint8_t *buf, uint32_t msglen) uint8_t *msgend = &buf[msglen-MESSAGE_TRAILER_SIZE]; while (p < msgend) { // Parse command - uint_fast8_t cmdid = *p++; + uint_fast16_t cmdid = command_parse_msgid(&p); const struct command_parser *cp = &SHARED_MEM->command_index[cmdid]; if (!cmdid || cmdid >= SHARED_MEM->command_index_size || cp->num_args > ARRAY_SIZE(SHARED_MEM->next_command_args)) { From 0d87bec159eba63744d820b5beaef5da20388ba4 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Tue, 11 Jun 2024 16:14:38 -0400 Subject: [PATCH 162/190] ci-install: update gnu-pru to version 2024.05 Signed-off-by: Eric Callahan --- scripts/ci-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci-install.sh b/scripts/ci-install.sh index a7d2599a29f8..f5a18612ffa8 100755 --- a/scripts/ci-install.sh +++ b/scripts/ci-install.sh @@ -35,7 +35,7 @@ if [ ! -f ${PRU_FILE} ]; then cd ${BUILD_DIR} git config --global user.email "you@example.com" git config --global user.name "Your Name" - git clone https://github.com/dinuxbg/gnupru -b 2023.01 --depth 1 + git clone https://github.com/dinuxbg/gnupru -b 2024.05 --depth 1 cd gnupru export PREFIX=${PRU_DIR} ./download-and-prepare.sh 2>&1 | pv -nli 30 > ${BUILD_DIR}/gnupru-build.log From 8de7153952c7513adec9f35a8d05b9f8bd00710a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 31 May 2024 13:17:41 -0400 Subject: [PATCH 163/190] probe: Rework ProbePointsHelper to store results locally Store the results of each probe attempt in a local "results" variable (instead of a class variable) when performing "automatic" probes. This is in preparation for gathering the results in the probing implementation. Signed-off-by: Kevin O'Connor --- klippy/extras/probe.py | 66 +++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index 5a976a06c150..944956ea72e0 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -386,7 +386,7 @@ def __init__(self, config, finalize_callback, default_points=None): # Internal probing state self.lift_speed = self.speed self.probe_offsets = (0., 0., 0.) - self.results = [] + self.manual_results = [] def minimum_points(self,n): if len(self.probe_points) < n: raise self.printer.config_error( @@ -398,34 +398,33 @@ def use_xy_offsets(self, use_offsets): self.use_offsets = use_offsets def get_lift_speed(self): return self.lift_speed - def _move_next(self): - toolhead = self.printer.lookup_object('toolhead') - # Lift toolhead + def _move(self, coord, speed): + self.printer.lookup_object('toolhead').manual_move(coord, speed) + def _raise_tool(self, is_first=False): speed = self.lift_speed - if not self.results: + if is_first: # Use full speed to first probe position speed = self.speed - toolhead.manual_move([None, None, self.horizontal_move_z], speed) - # Check if done probing - if len(self.results) >= len(self.probe_points): - toolhead.get_last_move_time() - res = self.finalize_callback(self.probe_offsets, self.results) - if res != "retry": - return True - self.results = [] + self._move([None, None, self.horizontal_move_z], speed) + def _invoke_callback(self, results): + # Flush lookahead queue + toolhead = self.printer.lookup_object('toolhead') + toolhead.get_last_move_time() + # Invoke callback + res = self.finalize_callback(self.probe_offsets, results) + return res != "retry" + def _move_next(self, probe_num): # Move to next XY probe point - nextpos = list(self.probe_points[len(self.results)]) + nextpos = list(self.probe_points[probe_num]) if self.use_offsets: nextpos[0] -= self.probe_offsets[0] nextpos[1] -= self.probe_offsets[1] - toolhead.manual_move(nextpos, self.speed) - return False + self._move(nextpos, self.speed) def start_probe(self, gcmd): manual_probe.verify_no_manual_probe(self.printer) # Lookup objects probe = self.printer.lookup_object('probe', None) method = gcmd.get('METHOD', 'automatic').lower() - self.results = [] def_move_z = self.default_horizontal_move_z self.horizontal_move_z = gcmd.get_float('HORIZONTAL_MOVE_Z', def_move_z) @@ -433,6 +432,7 @@ def start_probe(self, gcmd): # Manual probe self.lift_speed = self.speed self.probe_offsets = (0., 0., 0.) + self.manual_results = [] self._manual_probe_start() return # Perform automatic probing @@ -441,24 +441,36 @@ def start_probe(self, gcmd): if self.horizontal_move_z < self.probe_offsets[2]: raise gcmd.error("horizontal_move_z can't be less than" " probe's z_offset") + results = [] probe_session = probe.start_probe_session(gcmd) while 1: - done = self._move_next() - if done: - break + self._raise_tool(not results) + if len(results) >= len(self.probe_points): + done = self._invoke_callback(results) + if done: + break + # Caller wants a "retry" - clear results and restart probing + results = [] + self._move_next(len(results)) pos = probe_session.run_probe(gcmd) - self.results.append(pos) + results.append(pos) probe_session.end_probe_session() def _manual_probe_start(self): - done = self._move_next() - if not done: - gcmd = self.gcode.create_gcode_command("", "", {}) - manual_probe.ManualProbeHelper(self.printer, gcmd, - self._manual_probe_finalize) + self._raise_tool(not self.manual_results) + if len(self.manual_results) >= len(self.probe_points): + done = self._invoke_callback(self.manual_results) + if done: + return + # Caller wants a "retry" - clear results and restart probing + self.manual_results = [] + self._move_next(len(self.manual_results)) + gcmd = self.gcode.create_gcode_command("", "", {}) + manual_probe.ManualProbeHelper(self.printer, gcmd, + self._manual_probe_finalize) def _manual_probe_finalize(self, kin_pos): if kin_pos is None: return - self.results.append(kin_pos) + self.manual_results.append(kin_pos) self._manual_probe_start() # Helper to obtain a single probe measurement From 1591a51f76cff336f0109bf4459bfa46dca1eaa3 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 31 May 2024 14:10:34 -0400 Subject: [PATCH 164/190] probe: Gather multiple results in ProbeSessionHelper Change run_probe() to gather the results locally, and introduce a new pull_probed_results() method that returns the previously probed results. This is in preparation for future probing code that benefits from batching probe results. Signed-off-by: Kevin O'Connor --- klippy/extras/probe.py | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index 944956ea72e0..6a69d1b5a7ad 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -128,14 +128,16 @@ def cmd_PROBE_ACCURACY(self, gcmd): fo_gcmd = gcode.create_gcode_command("", "", fo_params) # Probe bed sample_count times probe_session = self.probe.start_probe_session(fo_gcmd) - positions = [] - while len(positions) < sample_count: + probe_num = 0 + while probe_num < sample_count: # Probe position - pos = probe_session.run_probe(fo_gcmd) - positions.append(pos) + probe_session.run_probe(fo_gcmd) + probe_num += 1 # Retract + pos = toolhead.get_position() liftpos = [None, None, pos[2] + params['sample_retract_dist']] self._move(liftpos, params['lift_speed']) + positions = probe_session.pull_probed_results() probe_session.end_probe_session() # Calculate maximum, minimum and average values max_value = max([p[2] for p in positions]) @@ -259,6 +261,7 @@ def __init__(self, config, mcu_probe): minval=0) # Session state self.multi_probe_pending = False + self.results = [] # Register event handlers self.printer.register_event_handler("gcode:command_error", self._handle_command_error) @@ -276,10 +279,12 @@ def start_probe_session(self, gcmd): self._probe_state_error() self.mcu_probe.multi_probe_begin() self.multi_probe_pending = True + self.results = [] return self def end_probe_session(self): if not self.multi_probe_pending: self._probe_state_error() + self.results = [] self.multi_probe_pending = False self.mcu_probe.multi_probe_end() def get_probe_params(self, gcmd=None): @@ -349,8 +354,13 @@ def run_probe(self, gcmd): toolhead.manual_move( probexy + [pos[2] + params['sample_retract_dist']], params['lift_speed']) - # Calculate and return result - return calc_probe_z_average(positions, params['samples_result']) + # Calculate result + epos = calc_probe_z_average(positions, params['samples_result']) + self.results.append(epos) + def pull_probed_results(self): + res = self.results + self.results = [] + return res # Helper to read the xyz probe offsets from the config class ProbeOffsetsHelper: @@ -441,19 +451,20 @@ def start_probe(self, gcmd): if self.horizontal_move_z < self.probe_offsets[2]: raise gcmd.error("horizontal_move_z can't be less than" " probe's z_offset") - results = [] probe_session = probe.start_probe_session(gcmd) + probe_num = 0 while 1: - self._raise_tool(not results) - if len(results) >= len(self.probe_points): + self._raise_tool(not probe_num) + if probe_num >= len(self.probe_points): + results = probe_session.pull_probed_results() done = self._invoke_callback(results) if done: break - # Caller wants a "retry" - clear results and restart probing - results = [] - self._move_next(len(results)) - pos = probe_session.run_probe(gcmd) - results.append(pos) + # Caller wants a "retry" - restart probing + probe_num = 0 + self._move_next(probe_num) + probe_session.run_probe(gcmd) + probe_num += 1 probe_session.end_probe_session() def _manual_probe_start(self): self._raise_tool(not self.manual_results) @@ -476,7 +487,8 @@ def _manual_probe_finalize(self, kin_pos): # Helper to obtain a single probe measurement def run_single_probe(probe, gcmd): probe_session = probe.start_probe_session(gcmd) - pos = probe_session.run_probe(gcmd) + probe_session.run_probe(gcmd) + pos = probe_session.pull_probed_results()[0] probe_session.end_probe_session() return pos From bf1bc1ee0f8b9baac322ffaa00d54afb02c2bd52 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 31 May 2024 15:46:34 -0400 Subject: [PATCH 165/190] probe_eddy_current: Introduce new EddyGatherSamples helper class Split the sample gathering code out of EddyEndstopWrapper class and into a new EddyGatherSamples class. Signed-off-by: Kevin O'Connor --- klippy/extras/probe_eddy_current.py | 147 ++++++++++++++++------------ 1 file changed, 84 insertions(+), 63 deletions(-) diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index 2fbde3960874..848f4d790037 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -183,38 +183,92 @@ def cmd_EDDY_CALIBRATE(self, gcmd): manual_probe.ManualProbeHelper(self.printer, gcmd, self.post_manual_probe) -# Helper for implementing PROBE style commands -class EddyEndstopWrapper: - REASON_SENSOR_ERROR = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 1 - def __init__(self, config, sensor_helper, calibration): - self._printer = config.get_printer() +# Tool to gather samples and convert them to probe positions +class EddyGatherSamples: + def __init__(self, printer, sensor_helper, calibration, z_offset): + self._printer = printer self._sensor_helper = sensor_helper - self._mcu = sensor_helper.get_mcu() self._calibration = calibration - self._z_offset = config.getfloat('z_offset', minval=0.) - self._dispatch = mcu.TriggerDispatch(self._mcu) + self._z_offset = z_offset + # Results storage self._samples = [] - self._is_sampling = self._start_from_home = self._need_stop = False - self._trigger_time = 0. - # Measurement gathering - def _start_measurements(self, is_home=False): + self._probe_times = [] self._need_stop = False - if self._is_sampling: - return - self._is_sampling = True - self._start_from_home = is_home - self._sensor_helper.add_client(self._add_measurement) - def _stop_measurements(self, is_home=False): - if not self._is_sampling or (is_home and not self._start_from_home): - return - self._need_stop = True + # Start samples + if not self._calibration.is_calibrated(): + raise self._printer.command_error( + "Must calibrate probe_eddy_current first") + sensor_helper.add_client(self._add_measurement) def _add_measurement(self, msg): if self._need_stop: del self._samples[:] - self._is_sampling = self._need_stop = False return False self._samples.append(msg) return True + def finish(self): + self._need_stop = True + def _await_samples(self, end_time): + # Make sure enough samples have been collected + reactor = self._printer.get_reactor() + mcu = self._sensor_helper.get_mcu() + while 1: + if self._samples and self._samples[-1]['data'][-1][0] >= end_time: + break + systime = reactor.monotonic() + est_print_time = mcu.estimated_print_time(systime) + if est_print_time > end_time + 1.0: + raise self._printer.command_error( + "probe_eddy_current sensor outage") + reactor.pause(systime + 0.010) + def _pull_position(self, start_time, end_time): + # Find average sensor position between time range + msg_num = discard_msgs = 0 + samp_sum = 0. + samp_count = 0 + while msg_num < len(self._samples): + msg = self._samples[msg_num] + msg_num += 1 + data = msg['data'] + if data[0][0] > end_time: + break + if data[-1][0] < start_time: + discard_msgs = msg_num + continue + for time, freq, z in data: + if time >= start_time and time <= end_time: + samp_sum += z + samp_count += 1 + del self._samples[:discard_msgs] + if not samp_count: + raise self._printer.command_error( + "Unable to obtain probe_eddy_current sensor readings") + return samp_sum / samp_count + def pull_probed(self): + results = [] + for start_time, end_time, toolhead_pos in self._probe_times: + self._await_samples(end_time) + sensor_z = self._pull_position(start_time, end_time) + # Callers expect position relative to z_offset, so recalculate + bed_deviation = toolhead_pos[2] - sensor_z + toolhead_pos[2] = self._z_offset + bed_deviation + results.append(toolhead_pos) + del self._probe_times[:] + return results + def note_probe(self, start_time, end_time, toolhead_pos): + self._probe_times.append((start_time, end_time, toolhead_pos)) + +# Helper for implementing PROBE style commands (descend until trigger) +class EddyEndstopWrapper: + REASON_SENSOR_ERROR = mcu.MCU_trsync.REASON_COMMS_TIMEOUT + 1 + def __init__(self, config, sensor_helper, calibration): + self._printer = config.get_printer() + self._sensor_helper = sensor_helper + self._mcu = sensor_helper.get_mcu() + self._calibration = calibration + self._z_offset = config.getfloat('z_offset', minval=0.) + self._dispatch = mcu.TriggerDispatch(self._mcu) + self._trigger_time = 0. + self._gather = None # Interface for MCU_endstop def get_mcu(self): return self._mcu @@ -225,7 +279,6 @@ def get_steppers(self): def home_start(self, print_time, sample_time, sample_count, rest_time, triggered=True): self._trigger_time = 0. - self._start_measurements(is_home=True) trigger_freq = self._calibration.height_to_freq(self._z_offset) trigger_completion = self._dispatch.start(print_time) self._sensor_helper.setup_home( @@ -235,7 +288,6 @@ def home_start(self, print_time, sample_time, sample_count, rest_time, def home_wait(self, home_end_time): self._dispatch.wait_end(home_end_time) trigger_time = self._sensor_helper.clear_home() - self._stop_measurements(is_home=True) res = self._dispatch.stop() if res >= mcu.MCU_trsync.REASON_COMMS_TIMEOUT: if res == mcu.MCU_trsync.REASON_COMMS_TIMEOUT: @@ -257,50 +309,19 @@ def probing_move(self, pos, speed): trig_pos = phoming.probing_move(self, pos, speed) if not self._trigger_time: return trig_pos - # Wait for samples to arrive + # Extract samples start_time = self._trigger_time + 0.050 end_time = start_time + 0.100 - reactor = self._printer.get_reactor() - while 1: - if self._samples and self._samples[-1]['data'][-1][0] >= end_time: - break - systime = reactor.monotonic() - est_print_time = self._mcu.estimated_print_time(systime) - if est_print_time > self._trigger_time + 1.0: - raise self._printer.command_error( - "probe_eddy_current sensor outage") - reactor.pause(systime + 0.010) - # Find position since trigger - samples = self._samples - self._samples = [] - samp_sum = 0. - samp_count = 0 - for msg in samples: - data = msg['data'] - if data[0][0] > end_time: - break - if data[-1][0] < start_time: - continue - for time, freq, z in data: - if time >= start_time and time <= end_time: - samp_sum += z - samp_count += 1 - if not samp_count: - raise self._printer.command_error( - "Unable to obtain probe_eddy_current sensor readings") - halt_z = samp_sum / samp_count - # Calculate reported "trigger" position toolhead = self._printer.lookup_object("toolhead") - new_pos = toolhead.get_position() - new_pos[2] += self._z_offset - halt_z - return new_pos + toolhead_pos = toolhead.get_position() + self._gather.note_probe(start_time, end_time, toolhead_pos) + return self._gather.pull_probed()[0] def multi_probe_begin(self): - if not self._calibration.is_calibrated(): - raise self._printer.command_error( - "Must calibrate probe_eddy_current first") - self._start_measurements() + self._gather = EddyGatherSamples(self._printer, self._sensor_helper, + self._calibration, self._z_offset) def multi_probe_end(self): - self._stop_measurements() + self._gather.finish() + self._gather = None def probe_prepare(self, hmove): pass def probe_finish(self, hmove): From 429aa2b2a6d48246331af38929d358e265e0ef19 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 31 May 2024 16:52:07 -0400 Subject: [PATCH 166/190] probe_eddy_current: Generate Z height from average frequency Calculate the average frequency from a set of samples, and then calculate the estimated Z height from that frequency. This may improve accuracy, as the frequency to Z height is not linear and averaging after the non-linear transform could bias the results. Signed-off-by: Kevin O'Connor --- klippy/extras/probe_eddy_current.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index 848f4d790037..dbb3f1d39b62 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -7,6 +7,8 @@ import mcu from . import ldc1612, probe, manual_probe +OUT_OF_RANGE = 99.9 + # Tool for calibrating the sensor Z detection and applying that calibration class EddyCalibration: def __init__(self, config): @@ -38,9 +40,9 @@ def apply_calibration(self, samples): for i, (samp_time, freq, dummy_z) in enumerate(samples): pos = bisect.bisect(self.cal_freqs, freq) if pos >= len(self.cal_zpos): - zpos = -99.9 + zpos = -OUT_OF_RANGE elif pos == 0: - zpos = 99.9 + zpos = OUT_OF_RANGE else: # XXX - could further optimize and avoid div by zero this_freq = self.cal_freqs[pos] @@ -51,6 +53,10 @@ def apply_calibration(self, samples): offset = prev_zpos - prev_freq * gain zpos = freq * gain + offset samples[i] = (samp_time, freq, round(zpos, 6)) + def freq_to_height(self, freq): + dummy_sample = [(0., freq, 0.)] + self.apply_calibration(dummy_sample) + return dummy_sample[0][2] def height_to_freq(self, height): # XXX - could optimize lookup rev_zpos = list(reversed(self.cal_zpos)) @@ -220,8 +226,8 @@ def _await_samples(self, end_time): raise self._printer.command_error( "probe_eddy_current sensor outage") reactor.pause(systime + 0.010) - def _pull_position(self, start_time, end_time): - # Find average sensor position between time range + def _pull_freq(self, start_time, end_time): + # Find average sensor frequency between time range msg_num = discard_msgs = 0 samp_sum = 0. samp_count = 0 @@ -236,7 +242,7 @@ def _pull_position(self, start_time, end_time): continue for time, freq, z in data: if time >= start_time and time <= end_time: - samp_sum += z + samp_sum += freq samp_count += 1 del self._samples[:discard_msgs] if not samp_count: @@ -247,7 +253,11 @@ def pull_probed(self): results = [] for start_time, end_time, toolhead_pos in self._probe_times: self._await_samples(end_time) - sensor_z = self._pull_position(start_time, end_time) + freq = self._pull_freq(start_time, end_time) + sensor_z = self._calibration.freq_to_height(freq) + if sensor_z <= -OUT_OF_RANGE or sensor_z >= OUT_OF_RANGE: + raise self._printer.command_error( + "probe_eddy_current sensor not in valid range") # Callers expect position relative to z_offset, so recalculate bed_deviation = toolhead_pos[2] - sensor_z toolhead_pos[2] = self._z_offset + bed_deviation From 49f511e6798bae55fd25df8662eb6d5234f17280 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 6 Jun 2024 16:15:51 -0400 Subject: [PATCH 167/190] probe_eddy_current: Process samples as they arrive Convert samples into probe frequencies as the samples arrive. Signed-off-by: Kevin O'Connor --- klippy/extras/probe_eddy_current.py | 32 ++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index dbb3f1d39b62..f661560e5128 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -199,6 +199,7 @@ def __init__(self, printer, sensor_helper, calibration, z_offset): # Results storage self._samples = [] self._probe_times = [] + self._probe_results = [] self._need_stop = False # Start samples if not self._calibration.is_calibrated(): @@ -210,16 +211,16 @@ def _add_measurement(self, msg): del self._samples[:] return False self._samples.append(msg) + self._check_samples() return True def finish(self): self._need_stop = True - def _await_samples(self, end_time): + def _await_samples(self): # Make sure enough samples have been collected reactor = self._printer.get_reactor() mcu = self._sensor_helper.get_mcu() - while 1: - if self._samples and self._samples[-1]['data'][-1][0] >= end_time: - break + while self._probe_times: + start_time, end_time, toolhead_pos = self._probe_times[0] systime = reactor.monotonic() est_print_time = mcu.estimated_print_time(systime) if est_print_time > end_time + 1.0: @@ -246,14 +247,24 @@ def _pull_freq(self, start_time, end_time): samp_count += 1 del self._samples[:discard_msgs] if not samp_count: - raise self._printer.command_error( - "Unable to obtain probe_eddy_current sensor readings") + # No sensor readings - raise error in pull_probed() + return 0. return samp_sum / samp_count + def _check_samples(self): + while self._samples and self._probe_times: + start_time, end_time, toolhead_pos = self._probe_times[0] + if self._samples[-1]['data'][-1][0] < end_time: + break + freq = self._pull_freq(start_time, end_time) + self._probe_results.append((freq, toolhead_pos)) + self._probe_times.pop(0) def pull_probed(self): + self._await_samples() results = [] - for start_time, end_time, toolhead_pos in self._probe_times: - self._await_samples(end_time) - freq = self._pull_freq(start_time, end_time) + for freq, toolhead_pos in self._probe_results: + if not freq: + raise self._printer.command_error( + "Unable to obtain probe_eddy_current sensor readings") sensor_z = self._calibration.freq_to_height(freq) if sensor_z <= -OUT_OF_RANGE or sensor_z >= OUT_OF_RANGE: raise self._printer.command_error( @@ -262,10 +273,11 @@ def pull_probed(self): bed_deviation = toolhead_pos[2] - sensor_z toolhead_pos[2] = self._z_offset + bed_deviation results.append(toolhead_pos) - del self._probe_times[:] + del self._probe_results[:] return results def note_probe(self, start_time, end_time, toolhead_pos): self._probe_times.append((start_time, end_time, toolhead_pos)) + self._check_samples() # Helper for implementing PROBE style commands (descend until trigger) class EddyEndstopWrapper: From aa0dbf6ee652fe87b4fcc828482309c6830f0efb Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 6 Jun 2024 16:39:33 -0400 Subject: [PATCH 168/190] probe_eddy_current: Calculate toolhead position along with probed position Support calculating the low-level kinematic toolhead position while calculating the probed frequency. Signed-off-by: Kevin O'Connor --- klippy/extras/probe_eddy_current.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index f661560e5128..7ec2305eb0b1 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -220,7 +220,7 @@ def _await_samples(self): reactor = self._printer.get_reactor() mcu = self._sensor_helper.get_mcu() while self._probe_times: - start_time, end_time, toolhead_pos = self._probe_times[0] + start_time, end_time, pos_time, toolhead_pos = self._probe_times[0] systime = reactor.monotonic() est_print_time = mcu.estimated_print_time(systime) if est_print_time > end_time + 1.0: @@ -250,12 +250,21 @@ def _pull_freq(self, start_time, end_time): # No sensor readings - raise error in pull_probed() return 0. return samp_sum / samp_count + def _lookup_toolhead_pos(self, pos_time): + toolhead = self._printer.lookup_object('toolhead') + kin = toolhead.get_kinematics() + kin_spos = {s.get_name(): s.mcu_to_commanded_position( + s.get_past_mcu_position(pos_time)) + for s in kin.get_steppers()} + return kin.calc_position(kin_spos) def _check_samples(self): while self._samples and self._probe_times: - start_time, end_time, toolhead_pos = self._probe_times[0] + start_time, end_time, pos_time, toolhead_pos = self._probe_times[0] if self._samples[-1]['data'][-1][0] < end_time: break freq = self._pull_freq(start_time, end_time) + if pos_time is not None: + toolhead_pos = self._lookup_toolhead_pos(pos_time) self._probe_results.append((freq, toolhead_pos)) self._probe_times.pop(0) def pull_probed(self): @@ -276,7 +285,10 @@ def pull_probed(self): del self._probe_results[:] return results def note_probe(self, start_time, end_time, toolhead_pos): - self._probe_times.append((start_time, end_time, toolhead_pos)) + self._probe_times.append((start_time, end_time, None, toolhead_pos)) + self._check_samples() + def note_probe_and_position(self, start_time, end_time, pos_time): + self._probe_times.append((start_time, end_time, pos_time, None)) self._check_samples() # Helper for implementing PROBE style commands (descend until trigger) From fcf064ba6851042a12a71975c229a9670b34cf31 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 31 May 2024 16:48:55 -0400 Subject: [PATCH 169/190] probe_eddy_current: Add support for probing in "scan" mode When probing in "scan" mode, the toolhead will pause at each position, but does not descend. This can notably reduce the total probing time. Signed-off-by: Kevin O'Connor --- klippy/extras/probe.py | 2 +- klippy/extras/probe_eddy_current.py | 33 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index 6a69d1b5a7ad..88aed25fe1a4 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -438,7 +438,7 @@ def start_probe(self, gcmd): def_move_z = self.default_horizontal_move_z self.horizontal_move_z = gcmd.get_float('HORIZONTAL_MOVE_Z', def_move_z) - if probe is None or method != 'automatic': + if probe is None or method == 'manual': # Manual probe self.lift_speed = self.speed self.probe_offsets = (0., 0., 0.) diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index 7ec2305eb0b1..de1f84476f05 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -363,6 +363,34 @@ def probe_finish(self, hmove): def get_position_endstop(self): return self._z_offset +# Implementing probing with "METHOD=scan" +class EddyScanningProbe: + def __init__(self, printer, sensor_helper, calibration, z_offset, gcmd): + self._printer = printer + self._sensor_helper = sensor_helper + self._calibration = calibration + self._z_offset = z_offset + self._gather = EddyGatherSamples(printer, sensor_helper, + calibration, z_offset) + self._sample_time_delay = 0.050 + self._sample_time = 0.100 + def run_probe(self, gcmd): + toolhead = self._printer.lookup_object("toolhead") + printtime = toolhead.get_last_move_time() + toolhead.dwell(self._sample_time_delay + self._sample_time) + start_time = printtime + self._sample_time_delay + self._gather.note_probe_and_position( + start_time, start_time + self._sample_time, start_time) + def pull_probed_results(self): + results = self._gather.pull_probed() + # Allow axis_twist_compensation to update results + for epos in results: + self._printer.send_event("probe:update_results", epos) + return results + def end_probe_session(self): + self._gather.finish() + self._gather = None + # Main "printer object" class PrinterEddyProbe: def __init__(self, config): @@ -389,6 +417,11 @@ def get_offsets(self): def get_status(self, eventtime): return self.cmd_helper.get_status(eventtime) def start_probe_session(self, gcmd): + method = gcmd.get('METHOD', 'automatic').lower() + if method == 'scan': + z_offset = self.get_offsets()[2] + return EddyScanningProbe(self.printer, self.sensor_helper, + self.calibration, z_offset, gcmd) return self.probe_session.start_probe_session(gcmd) def load_config_prefix(config): From beba2c2d3380acc966e876ee02e4902ab3e71976 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 15 Jun 2024 11:03:28 -0400 Subject: [PATCH 170/190] axis_twist_compensation: No need to rename bed_mesh and manual_probe Signed-off-by: Kevin O'Connor --- klippy/extras/axis_twist_compensation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/klippy/extras/axis_twist_compensation.py b/klippy/extras/axis_twist_compensation.py index 8f4a581a7708..184e993102af 100644 --- a/klippy/extras/axis_twist_compensation.py +++ b/klippy/extras/axis_twist_compensation.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import math -from . import manual_probe as ManualProbe, bed_mesh as BedMesh +from . import manual_probe, bed_mesh DEFAULT_SAMPLE_COUNT = 3 @@ -53,9 +53,9 @@ def _update_z_compensation_value(self, pos): / (sample_count - 1)) interpolate_t = (x_coord - self.calibrate_start_x) / spacing interpolate_i = int(math.floor(interpolate_t)) - interpolate_i = BedMesh.constrain(interpolate_i, 0, sample_count - 2) + interpolate_i = bed_mesh.constrain(interpolate_i, 0, sample_count - 2) interpolate_t -= interpolate_i - interpolated_z_compensation = BedMesh.lerp( + interpolated_z_compensation = bed_mesh.lerp( interpolate_t, z_compensations[interpolate_i], z_compensations[interpolate_i + 1]) pos[2] += interpolated_z_compensation @@ -137,7 +137,7 @@ def cmd_AXIS_TWIST_COMPENSATION_CALIBRATE(self, gcmd): nozzle_points, self.probe_x_offset, self.probe_y_offset) # verify no other manual probe is in progress - ManualProbe.verify_no_manual_probe(self.printer) + manual_probe.verify_no_manual_probe(self.printer) # begin calibration self.current_point_index = 0 @@ -199,7 +199,7 @@ def _calibration(self, probe_points, nozzle_points, interval): self._move_helper((nozzle_points[self.current_point_index])) # start the manual (nozzle) probe - ManualProbe.ManualProbeHelper( + manual_probe.ManualProbeHelper( self.printer, self.gcmd, self._manual_probe_callback_factory( probe_points, nozzle_points, interval)) From 433fcb6f249406c8b5e2f25d1e870809beeafb40 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 15 Jun 2024 11:04:07 -0400 Subject: [PATCH 171/190] axis_twist_compensation: Fix missing probe import Fixes missing import introduced in commit bec47e04. Signed-off-by: Kevin O'Connor --- klippy/extras/axis_twist_compensation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/klippy/extras/axis_twist_compensation.py b/klippy/extras/axis_twist_compensation.py index 184e993102af..e7aad52c047f 100644 --- a/klippy/extras/axis_twist_compensation.py +++ b/klippy/extras/axis_twist_compensation.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import math -from . import manual_probe, bed_mesh +from . import manual_probe, bed_mesh, probe DEFAULT_SAMPLE_COUNT = 3 From ae227d485cdac859b22d77da4072f870bce07740 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 11 Jun 2024 08:58:31 -0400 Subject: [PATCH 172/190] armcm_link: Fix build on recent arm gcc/newlibc versions It seems recent arm gcc versions no longer build correctly using the "--specs=nano.specs --specs=nosys.specs" linker flags. Replace those linker flags with "-nostdlib -lgcc -lc_nano". Signed-off-by: Kevin O'Connor --- src/atsam/Makefile | 2 +- src/atsamd/Makefile | 2 +- src/generic/armcm_link.lds.S | 3 +++ src/hc32f460/Makefile | 2 +- src/lpc176x/Makefile | 2 +- src/rp2040/Makefile | 7 +++---- src/rp2040/rp2040_link.lds.S | 3 +++ src/stm32/Makefile | 2 +- 8 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/atsam/Makefile b/src/atsam/Makefile index 7ab69b823e00..3595d0cef40f 100644 --- a/src/atsam/Makefile +++ b/src/atsam/Makefile @@ -20,7 +20,7 @@ CFLAGS-$(CONFIG_MACH_SAM4E) += -Ilib/sam4e/include CFLAGS-$(CONFIG_MACH_SAME70) += -Ilib/same70b/include CFLAGS += $(CFLAGS-y) -D__$(MCU)__ -mthumb -Ilib/cmsis-core -Ilib/fast-hash -CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs +CFLAGS_klipper.elf += -nostdlib -lgcc -lc_nano CFLAGS_klipper.elf += -T $(OUT)src/generic/armcm_link.ld $(OUT)klipper.elf: $(OUT)src/generic/armcm_link.ld diff --git a/src/atsamd/Makefile b/src/atsamd/Makefile index d241cd8cbd23..8b9722b629fd 100644 --- a/src/atsamd/Makefile +++ b/src/atsamd/Makefile @@ -14,7 +14,7 @@ CFLAGS-$(CONFIG_MACH_SAME51) += -Ilib/same51/include CFLAGS-$(CONFIG_MACH_SAMX5) += -mcpu=cortex-m4 -Ilib/same54/include CFLAGS += $(CFLAGS-y) -D__$(MCU)__ -mthumb -Ilib/cmsis-core -Ilib/fast-hash -CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs +CFLAGS_klipper.elf += -nostdlib -lgcc -lc_nano CFLAGS_klipper.elf += -T $(OUT)src/generic/armcm_link.ld $(OUT)klipper.elf: $(OUT)src/generic/armcm_link.ld diff --git a/src/generic/armcm_link.lds.S b/src/generic/armcm_link.lds.S index 2f789f1301e3..94dd2100d596 100644 --- a/src/generic/armcm_link.lds.S +++ b/src/generic/armcm_link.lds.S @@ -69,5 +69,8 @@ SECTIONS // that isn't needed so no need to include them in the binary. *(.init) *(.fini) + // Don't include exception tables + *(.ARM.extab) + *(.ARM.exidx) } } diff --git a/src/hc32f460/Makefile b/src/hc32f460/Makefile index c44267369362..85d2fa19d692 100644 --- a/src/hc32f460/Makefile +++ b/src/hc32f460/Makefile @@ -7,7 +7,7 @@ dirs-y += src/hc32f460 src/generic lib/hc32f460/driver/src lib/hc32f460/mcu/comm CFLAGS += -mthumb -mcpu=cortex-m4 -Isrc/hc32f460 -Ilib/hc32f460/driver/inc -Ilib/hc32f460/mcu/common -Ilib/cmsis-core -DHC32F460 -CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs +CFLAGS_klipper.elf += -nostdlib -lgcc -lc_nano CFLAGS_klipper.elf += -T $(OUT)src/generic/armcm_link.ld $(OUT)klipper.elf: $(OUT)src/generic/armcm_link.ld diff --git a/src/lpc176x/Makefile b/src/lpc176x/Makefile index 7ed80b26bca1..6814969c5db7 100644 --- a/src/lpc176x/Makefile +++ b/src/lpc176x/Makefile @@ -7,7 +7,7 @@ dirs-y += src/lpc176x src/generic lib/lpc176x/device CFLAGS += -mthumb -mcpu=cortex-m3 -Ilib/lpc176x/device -Ilib/cmsis-core -CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs +CFLAGS_klipper.elf += -nostdlib -lgcc -lc_nano CFLAGS_klipper.elf += -T $(OUT)src/generic/armcm_link.ld $(OUT)klipper.elf: $(OUT)src/generic/armcm_link.ld diff --git a/src/rp2040/Makefile b/src/rp2040/Makefile index 64199014009a..b82503a39669 100644 --- a/src/rp2040/Makefile +++ b/src/rp2040/Makefile @@ -46,7 +46,6 @@ $(OUT)klipper.uf2: $(OUT)klipper.elf $(OUT)lib/rp2040/elf2uf2/elf2uf2 $(Q)$(OUT)lib/rp2040/elf2uf2/elf2uf2 $< $@ rptarget-$(CONFIG_RP2040_HAVE_STAGE2) := $(OUT)klipper.uf2 -rplink-$(CONFIG_RP2040_HAVE_STAGE2) := $(OUT)src/rp2040/rp2040_link.ld stage2-$(CONFIG_RP2040_HAVE_STAGE2) := $(OUT)stage2.o # rp2040 building when using a bootloader @@ -55,13 +54,13 @@ $(OUT)klipper.bin: $(OUT)klipper.elf $(Q)$(OBJCOPY) -O binary $< $@ rptarget-$(CONFIG_RP2040_HAVE_BOOTLOADER) := $(OUT)klipper.bin -rplink-$(CONFIG_RP2040_HAVE_BOOTLOADER) := $(OUT)src/rp2040/rp2040_link.ld # Set klipper.elf linker rules target-y += $(rptarget-y) -CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs -T $(rplink-y) +CFLAGS_klipper.elf += -nostdlib -lgcc -lc_nano +CFLAGS_klipper.elf += -T $(OUT)src/rp2040/rp2040_link.ld OBJS_klipper.elf += $(stage2-y) -$(OUT)klipper.elf: $(stage2-y) $(rplink-y) +$(OUT)klipper.elf: $(stage2-y) $(OUT)src/rp2040/rp2040_link.ld # Flash rules lib/rp2040_flash/rp2040_flash: diff --git a/src/rp2040/rp2040_link.lds.S b/src/rp2040/rp2040_link.lds.S index 9b0264a2b941..6c2db3c9bb58 100644 --- a/src/rp2040/rp2040_link.lds.S +++ b/src/rp2040/rp2040_link.lds.S @@ -77,5 +77,8 @@ SECTIONS // that isn't needed so no need to include them in the binary. *(.init) *(.fini) + // Don't include exception tables + *(.ARM.extab) + *(.ARM.exidx) } } diff --git a/src/stm32/Makefile b/src/stm32/Makefile index 18af2e9d7b4e..5f4d3af5cc5b 100644 --- a/src/stm32/Makefile +++ b/src/stm32/Makefile @@ -31,7 +31,7 @@ CFLAGS-$(CONFIG_MACH_STM32H7) += -mcpu=cortex-m7 -Ilib/stm32h7/include CFLAGS-$(CONFIG_MACH_STM32L4) += -mcpu=cortex-m4 -Ilib/stm32l4/include CFLAGS += $(CFLAGS-y) -D$(MCU_UPPER) -mthumb -Ilib/cmsis-core -Ilib/fast-hash -CFLAGS_klipper.elf += --specs=nano.specs --specs=nosys.specs +CFLAGS_klipper.elf += -nostdlib -lgcc -lc_nano CFLAGS_klipper.elf += -T $(OUT)src/generic/armcm_link.ld $(OUT)klipper.elf: $(OUT)src/generic/armcm_link.ld From 863a463cb2d07fce2ae09e2b0f925bdf201051dc Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 11 Jun 2024 10:50:57 -0400 Subject: [PATCH 173/190] rp2040_link: Explicitly set klipper.elf output section flags to avoid warning Avoid pointless "LOAD segment with RWX permissions" linker warnings during the rp2040 build. Signed-off-by: Kevin O'Connor --- src/rp2040/rp2040_link.lds.S | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/rp2040/rp2040_link.lds.S b/src/rp2040/rp2040_link.lds.S index 6c2db3c9bb58..abc5be6c5d2f 100644 --- a/src/rp2040/rp2040_link.lds.S +++ b/src/rp2040/rp2040_link.lds.S @@ -21,6 +21,16 @@ MEMORY ram (rwx) : ORIGIN = CONFIG_RAM_START , LENGTH = CONFIG_RAM_SIZE } +// Force flags for each output section to avoid RWX linker warning +PHDRS +{ + text_segment PT_LOAD FLAGS(5); // RX flags + ram_vectortable_segment PT_LOAD FLAGS(6); // RW flags + data_segment PT_LOAD FLAGS(6); // RW flags + bss_segment PT_LOAD FLAGS(6); // RW flags + stack_segment PT_LOAD FLAGS(6); // RW flags +} + SECTIONS { .text : { @@ -32,7 +42,7 @@ SECTIONS KEEP(*(.vector_table)) _text_vectortable_end = .; *(.text.armcm_boot*) - } > rom + } > rom :text_segment . = ALIGN(4); _data_flash = .; @@ -41,7 +51,7 @@ SECTIONS _ram_vectortable_start = .; . = . + ( _text_vectortable_end - _text_vectortable_start ) ; _ram_vectortable_end = .; - } > ram + } > ram :ram_vectortable_segment .data : AT (_data_flash) { @@ -53,7 +63,7 @@ SECTIONS *(.data .data.*); . = ALIGN(4); _data_end = .; - } > ram + } > ram :data_segment .bss (NOLOAD) : { @@ -63,14 +73,14 @@ SECTIONS *(COMMON) . = ALIGN(4); _bss_end = .; - } > ram + } > ram :bss_segment _stack_start = CONFIG_RAM_START + CONFIG_RAM_SIZE - CONFIG_STACK_SIZE ; .stack _stack_start (NOLOAD) : { . = . + CONFIG_STACK_SIZE; _stack_end = .; - } > ram + } > ram :stack_segment /DISCARD/ : { // The .init/.fini sections are used by __libc_init_array(), but From 11f04ba1bae774d32fd43bed4a0772c58d8df199 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 17 Jun 2024 13:49:05 -0400 Subject: [PATCH 174/190] configfile: Allow getchoice() to take a list If a list is passed to getchoice(), seamlessly convert it to a dict. Signed-off-by: Kevin O'Connor --- klippy/configfile.py | 2 ++ klippy/extras/bltouch.py | 2 +- klippy/extras/display/hd44780.py | 2 +- klippy/extras/display/hd44780_spi.py | 2 +- klippy/extras/display/menu_keys.py | 2 +- klippy/extras/probe.py | 2 +- klippy/extras/replicape.py | 2 +- klippy/kinematics/cartesian.py | 2 +- klippy/kinematics/hybrid_corexy.py | 2 +- klippy/kinematics/hybrid_corexz.py | 2 +- klippy/mcu.py | 3 +-- 11 files changed, 12 insertions(+), 11 deletions(-) diff --git a/klippy/configfile.py b/klippy/configfile.py index 91b555cdef75..a8a4a4ff76f3 100644 --- a/klippy/configfile.py +++ b/klippy/configfile.py @@ -69,6 +69,8 @@ def getboolean(self, option, default=sentinel, note_valid=True): return self._get_wrapper(self.fileconfig.getboolean, option, default, note_valid=note_valid) def getchoice(self, option, choices, default=sentinel, note_valid=True): + if type(choices) == type([]): + choices = {i: i for i in choices} if choices and type(list(choices.keys())[0]) == int: c = self.getint(option, default, note_valid=note_valid) else: diff --git a/klippy/extras/bltouch.py b/klippy/extras/bltouch.py index 58f66819732d..ae461f4b83be 100644 --- a/klippy/extras/bltouch.py +++ b/klippy/extras/bltouch.py @@ -42,7 +42,7 @@ def __init__(self, config): # Create an "endstop" object to handle the sensor pin self.mcu_endstop = ppins.setup_pin('endstop', config.get('sensor_pin')) # output mode - omodes = {'5V': '5V', 'OD': 'OD', None: None} + omodes = ['5V', 'OD', None] self.output_mode = config.getchoice('set_output_mode', omodes, None) # Setup for sensor test self.next_test_time = 0. diff --git a/klippy/extras/display/hd44780.py b/klippy/extras/display/hd44780.py index 9adfa20f7992..2da49c51ed16 100644 --- a/klippy/extras/display/hd44780.py +++ b/klippy/extras/display/hd44780.py @@ -8,7 +8,7 @@ BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000 LINE_LENGTH_DEFAULT=20 -LINE_LENGTH_OPTIONS={16:16, 20:20} +LINE_LENGTH_OPTIONS=[16, 20] TextGlyphs = { 'right_arrow': b'\x7e' } diff --git a/klippy/extras/display/hd44780_spi.py b/klippy/extras/display/hd44780_spi.py index cd1d9e3ea773..f21accbb4291 100644 --- a/klippy/extras/display/hd44780_spi.py +++ b/klippy/extras/display/hd44780_spi.py @@ -9,7 +9,7 @@ from .. import bus LINE_LENGTH_DEFAULT=20 -LINE_LENGTH_OPTIONS={16:16, 20:20} +LINE_LENGTH_OPTIONS=[16, 20] TextGlyphs = { 'right_arrow': b'\x7e' } diff --git a/klippy/extras/display/menu_keys.py b/klippy/extras/display/menu_keys.py index 91a96e19fbaa..8094c9964ae9 100644 --- a/klippy/extras/display/menu_keys.py +++ b/klippy/extras/display/menu_keys.py @@ -18,7 +18,7 @@ def __init__(self, config, callback): # Register rotary encoder encoder_pins = config.get('encoder_pins', None) encoder_steps_per_detent = config.getchoice('encoder_steps_per_detent', - {2: 2, 4: 4}, 4) + [2, 4], 4) if encoder_pins is not None: try: pin1, pin2 = encoder_pins.split(',') diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index 88aed25fe1a4..c467e181e4d5 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -252,7 +252,7 @@ def __init__(self, config, mcu_probe): self.sample_count = config.getint('samples', 1, minval=1) self.sample_retract_dist = config.getfloat('sample_retract_dist', 2., above=0.) - atypes = {'median': 'median', 'average': 'average'} + atypes = ['median', 'average'] self.samples_result = config.getchoice('samples_result', atypes, 'average') self.samples_tolerance = config.getfloat('samples_tolerance', 0.100, diff --git a/klippy/extras/replicape.py b/klippy/extras/replicape.py index ab501cafc427..f7f7bb64bd22 100644 --- a/klippy/extras/replicape.py +++ b/klippy/extras/replicape.py @@ -160,7 +160,7 @@ def __init__(self, config): printer = config.get_printer() ppins = printer.lookup_object('pins') ppins.register_chip('replicape', self) - revisions = {'B3': 'B3'} + revisions = ['B3'] config.getchoice('revision', revisions) self.host_mcu = mcu.get_printer_mcu(printer, config.get('host_mcu')) # Setup enable pin diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index 9774672e25cc..0c4bb9255853 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -23,7 +23,7 @@ def __init__(self, toolhead, config): self.dc_module = None if config.has_section('dual_carriage'): dc_config = config.getsection('dual_carriage') - dc_axis = dc_config.getchoice('axis', {'x': 'x', 'y': 'y'}) + dc_axis = dc_config.getchoice('axis', ['x', 'y']) self.dual_carriage_axis = {'x': 0, 'y': 1}[dc_axis] # setup second dual carriage rail self.rails.append(stepper.LookupMultiRail(dc_config)) diff --git a/klippy/kinematics/hybrid_corexy.py b/klippy/kinematics/hybrid_corexy.py index e852826afba6..265a0e6da33d 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -27,7 +27,7 @@ def __init__(self, toolhead, config): if config.has_section('dual_carriage'): dc_config = config.getsection('dual_carriage') # dummy for cartesian config users - dc_config.getchoice('axis', {'x': 'x'}, default='x') + dc_config.getchoice('axis', ['x'], default='x') # setup second dual carriage rail self.rails.append(stepper.PrinterRail(dc_config)) self.rails[1].get_endstops()[0][0].add_stepper( diff --git a/klippy/kinematics/hybrid_corexz.py b/klippy/kinematics/hybrid_corexz.py index 58e6b0d390c9..2d89e3f7bd03 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -27,7 +27,7 @@ def __init__(self, toolhead, config): if config.has_section('dual_carriage'): dc_config = config.getsection('dual_carriage') # dummy for cartesian config users - dc_config.getchoice('axis', {'x': 'x'}, default='x') + dc_config.getchoice('axis', ['x'], default='x') # setup second dual carriage rail self.rails.append(stepper.PrinterRail(dc_config)) self.rails[2].get_endstops()[0][0].add_stepper( diff --git a/klippy/mcu.py b/klippy/mcu.py index 6b106245bdcd..23ba07173257 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -574,9 +574,8 @@ def __init__(self, config, clocksync): restart_methods = [None, 'arduino', 'cheetah', 'command', 'rpi_usb'] self._restart_method = 'command' if self._baud: - rmethods = {m: m for m in restart_methods} self._restart_method = config.getchoice('restart_method', - rmethods, None) + restart_methods, None) self._reset_cmd = self._config_reset_cmd = None self._is_mcu_bridge = False self._emergency_stop_cmd = None From 0a14e3315084065b19163104c3604d13a7c2dbc8 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 6 Jun 2024 17:09:02 -0400 Subject: [PATCH 175/190] probe_eddy_current: Add support for "rapid_scan" mode Add a scanning mode that does not require pausing the toolhead at each probe point. Signed-off-by: Kevin O'Connor --- klippy/extras/probe_eddy_current.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index de1f84476f05..345096e60b44 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -373,15 +373,27 @@ def __init__(self, printer, sensor_helper, calibration, z_offset, gcmd): self._gather = EddyGatherSamples(printer, sensor_helper, calibration, z_offset) self._sample_time_delay = 0.050 - self._sample_time = 0.100 + self._sample_time = gcmd.get_float("SAMPLE_TIME", 0.100, above=0.0) + self._is_rapid = gcmd.get("METHOD", "scan") == 'rapid_scan' + def _rapid_lookahead_cb(self, printtime): + start_time = printtime - self._sample_time / 2 + self._gather.note_probe_and_position( + start_time, start_time + self._sample_time, printtime) def run_probe(self, gcmd): toolhead = self._printer.lookup_object("toolhead") + if self._is_rapid: + toolhead.register_lookahead_callback(self._rapid_lookahead_cb) + return printtime = toolhead.get_last_move_time() toolhead.dwell(self._sample_time_delay + self._sample_time) start_time = printtime + self._sample_time_delay self._gather.note_probe_and_position( start_time, start_time + self._sample_time, start_time) def pull_probed_results(self): + if self._is_rapid: + # Flush lookahead (so all lookahead callbacks are invoked) + toolhead = self._printer.lookup_object("toolhead") + toolhead.get_last_move_time() results = self._gather.pull_probed() # Allow axis_twist_compensation to update results for epos in results: @@ -418,7 +430,7 @@ def get_status(self, eventtime): return self.cmd_helper.get_status(eventtime) def start_probe_session(self, gcmd): method = gcmd.get('METHOD', 'automatic').lower() - if method == 'scan': + if method in ('scan', 'rapid_scan'): z_offset = self.get_offsets()[2] return EddyScanningProbe(self.printer, self.sensor_helper, self.calibration, z_offset, gcmd) From f2df011c68aeff1c9240613837f91f04baa8da83 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Tue, 13 Feb 2024 14:55:14 -0500 Subject: [PATCH 176/190] bed_mesh: optimize rapid travel paths This adds supplemental path generation that implements "overshoot" when a change of direction is performed during a rapid scan. This overshoot reduces measurement error at the extremes of the mesh along the X axis. Signed-off-by: Eric Callahan --- klippy/extras/bed_mesh.py | 689 +++++++++++++++++++++++++++----------- 1 file changed, 495 insertions(+), 194 deletions(-) diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index 095ccf1fdc8c..d9ee7dfba8a6 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -298,130 +298,24 @@ def __init__(self, config, bedmesh): self.radius = self.origin = None self.mesh_min = self.mesh_max = (0., 0.) self.adaptive_margin = config.getfloat('adaptive_margin', 0.0) - self.zero_ref_pos = config.getfloatlist( - "zero_reference_position", None, count=2 - ) - self.zero_reference_mode = ZrefMode.DISABLED - self.faulty_regions = [] - self.substituted_indices = collections.OrderedDict() self.bedmesh = bedmesh self.mesh_config = collections.OrderedDict() self._init_mesh_config(config) - self._generate_points(config.error) + self.probe_mgr = ProbeManager( + config, self.orig_config, self.probe_finalize + ) + try: + self.probe_mgr.generate_points( + self.mesh_config, self.mesh_min, self.mesh_max, + self.radius, self.origin + ) + except BedMeshError as e: + raise config.error(str(e)) self._profile_name = "default" - self.probe_helper = probe.ProbePointsHelper( - config, self.probe_finalize, self._get_adjusted_points()) - self.probe_helper.minimum_points(3) - self.probe_helper.use_xy_offsets(True) self.gcode = self.printer.lookup_object('gcode') self.gcode.register_command( 'BED_MESH_CALIBRATE', self.cmd_BED_MESH_CALIBRATE, desc=self.cmd_BED_MESH_CALIBRATE_help) - def _generate_points(self, error, probe_method="automatic"): - x_cnt = self.mesh_config['x_count'] - y_cnt = self.mesh_config['y_count'] - min_x, min_y = self.mesh_min - max_x, max_y = self.mesh_max - x_dist = (max_x - min_x) / (x_cnt - 1) - y_dist = (max_y - min_y) / (y_cnt - 1) - # floor distances down to next hundredth - x_dist = math.floor(x_dist * 100) / 100 - y_dist = math.floor(y_dist * 100) / 100 - if x_dist < 1. or y_dist < 1.: - raise error("bed_mesh: min/max points too close together") - - if self.radius is not None: - # round bed, min/max needs to be recalculated - y_dist = x_dist - new_r = (x_cnt // 2) * x_dist - min_x = min_y = -new_r - max_x = max_y = new_r - else: - # rectangular bed, only re-calc max_x - max_x = min_x + x_dist * (x_cnt - 1) - pos_y = min_y - points = [] - for i in range(y_cnt): - for j in range(x_cnt): - if not i % 2: - # move in positive directon - pos_x = min_x + j * x_dist - else: - # move in negative direction - pos_x = max_x - j * x_dist - if self.radius is None: - # rectangular bed, append - points.append((pos_x, pos_y)) - else: - # round bed, check distance from origin - dist_from_origin = math.sqrt(pos_x*pos_x + pos_y*pos_y) - if dist_from_origin <= self.radius: - points.append( - (self.origin[0] + pos_x, self.origin[1] + pos_y)) - pos_y += y_dist - self.points = points - if self.zero_ref_pos is None or probe_method == "manual": - # Zero Reference Disabled - self.zero_reference_mode = ZrefMode.DISABLED - elif within(self.zero_ref_pos, self.mesh_min, self.mesh_max): - # Zero Reference position within mesh - self.zero_reference_mode = ZrefMode.IN_MESH - else: - # Zero Reference position outside of mesh - self.zero_reference_mode = ZrefMode.PROBE - if not self.faulty_regions: - return - self.substituted_indices.clear() - if self.zero_reference_mode == ZrefMode.PROBE: - # Cannot probe a reference within a faulty region - for min_c, max_c in self.faulty_regions: - if within(self.zero_ref_pos, min_c, max_c): - opt = "zero_reference_position" - raise error( - "bed_mesh: Cannot probe zero reference position at " - "(%.2f, %.2f) as it is located within a faulty region." - " Check the value for option '%s'" - % (self.zero_ref_pos[0], self.zero_ref_pos[1], opt,) - ) - # Check to see if any points fall within faulty regions - if probe_method == "manual": - return - last_y = self.points[0][1] - is_reversed = False - for i, coord in enumerate(self.points): - if not isclose(coord[1], last_y): - is_reversed = not is_reversed - last_y = coord[1] - adj_coords = [] - for min_c, max_c in self.faulty_regions: - if within(coord, min_c, max_c, tol=.00001): - # Point lies within a faulty region - adj_coords = [ - (min_c[0], coord[1]), (coord[0], min_c[1]), - (coord[0], max_c[1]), (max_c[0], coord[1])] - if is_reversed: - # Swap first and last points for zig-zag pattern - first = adj_coords[0] - adj_coords[0] = adj_coords[-1] - adj_coords[-1] = first - break - if not adj_coords: - # coord is not located within a faulty region - continue - valid_coords = [] - for ac in adj_coords: - # make sure that coordinates are within the mesh boundary - if self.radius is None: - if within(ac, (min_x, min_y), (max_x, max_y), .000001): - valid_coords.append(ac) - else: - dist_from_origin = math.sqrt(ac[0]*ac[0] + ac[1]*ac[1]) - if dist_from_origin <= self.radius: - valid_coords.append(ac) - if not valid_coords: - raise error("bed_mesh: Unable to generate coordinates" - " for faulty region at index: %d" % (i)) - self.substituted_indices[i] = valid_coords def print_generated_points(self, print_func): x_offset = y_offset = 0. probe = self.printer.lookup_object('probe', None) @@ -429,20 +323,23 @@ def print_generated_points(self, print_func): x_offset, y_offset = probe.get_offsets()[:2] print_func("bed_mesh: generated points\nIndex" " | Tool Adjusted | Probe") - for i, (x, y) in enumerate(self.points): + points = self.probe_mgr.get_base_points() + for i, (x, y) in enumerate(points): adj_pt = "(%.1f, %.1f)" % (x - x_offset, y - y_offset) mesh_pt = "(%.1f, %.1f)" % (x, y) print_func( " %-4d| %-16s| %s" % (i, adj_pt, mesh_pt)) - if self.zero_ref_pos is not None: + zero_ref_pos = self.probe_mgr.get_zero_ref_pos() + if zero_ref_pos is not None: print_func( "bed_mesh: zero_reference_position is (%.2f, %.2f)" - % (self.zero_ref_pos[0], self.zero_ref_pos[1]) + % (zero_ref_pos[0], zero_ref_pos[1]) ) - if self.substituted_indices: + substitutes = self.probe_mgr.get_substitutes() + if substitutes: print_func("bed_mesh: faulty region points") - for i, v in self.substituted_indices.items(): - pt = self.points[i] + for i, v in substitutes.items(): + pt = points[i] print_func("%d (%.2f, %.2f), substituted points: %s" % (i, pt[0], pt[1], repr(v))) def _init_mesh_config(self, config): @@ -481,42 +378,6 @@ def _init_mesh_config(self, config): config.get('algorithm', 'lagrange').strip().lower() orig_cfg['tension'] = mesh_cfg['tension'] = config.getfloat( 'bicubic_tension', .2, minval=0., maxval=2.) - for i in list(range(1, 100, 1)): - start = config.getfloatlist("faulty_region_%d_min" % (i,), None, - count=2) - if start is None: - break - end = config.getfloatlist("faulty_region_%d_max" % (i,), count=2) - # Validate the corners. If necessary reorganize them. - # c1 = min point, c3 = max point - # c4 ---- c3 - # | | - # c1 ---- c2 - c1 = [min([s, e]) for s, e in zip(start, end)] - c3 = [max([s, e]) for s, e in zip(start, end)] - c2 = [c1[0], c3[1]] - c4 = [c3[0], c1[1]] - # Check for overlapping regions - for j, (prev_c1, prev_c3) in enumerate(self.faulty_regions): - prev_c2 = [prev_c1[0], prev_c3[1]] - prev_c4 = [prev_c3[0], prev_c1[1]] - # Validate that no existing corner is within the new region - for coord in [prev_c1, prev_c2, prev_c3, prev_c4]: - if within(coord, c1, c3): - raise config.error( - "bed_mesh: Existing faulty_region_%d %s overlaps " - "added faulty_region_%d %s" - % (j+1, repr([prev_c1, prev_c3]), - i, repr([c1, c3]))) - # Validate that no new corner is within an existing region - for coord in [c1, c2, c3, c4]: - if within(coord, prev_c1, prev_c3): - raise config.error( - "bed_mesh: Added faulty_region_%d %s overlaps " - "existing faulty_region_%d %s" - % (i, repr([c1, c3]), - j+1, repr([prev_c1, prev_c3]))) - self.faulty_regions.append((c1, c3)) self._verify_algorithm(config.error) def _verify_algorithm(self, error): params = self.mesh_config @@ -712,47 +573,36 @@ def update_config(self, gcmd): if need_cfg_update: self._verify_algorithm(gcmd.error) - self._generate_points(gcmd.error, probe_method) + self.probe_mgr.generate_points( + self.mesh_config, self.mesh_min, self.mesh_max, + self.radius, self.origin, probe_method + ) gcmd.respond_info("Generating new points...") self.print_generated_points(gcmd.respond_info) - pts = self._get_adjusted_points() - self.probe_helper.update_probe_points(pts, 3) msg = "\n".join(["%s: %s" % (k, v) for k, v in self.mesh_config.items()]) logging.info("Updated Mesh Configuration:\n" + msg) else: - self._generate_points(gcmd.error, probe_method) - pts = self._get_adjusted_points() - self.probe_helper.update_probe_points(pts, 3) - def _get_adjusted_points(self): - adj_pts = [] - if self.substituted_indices: - last_index = 0 - for i, pts in self.substituted_indices.items(): - adj_pts.extend(self.points[last_index:i]) - adj_pts.extend(pts) - # Add one to the last index to skip the point - # we are replacing - last_index = i + 1 - adj_pts.extend(self.points[last_index:]) - else: - adj_pts = list(self.points) - if self.zero_reference_mode == ZrefMode.PROBE: - adj_pts.append(self.zero_ref_pos) - return adj_pts + self.probe_mgr.generate_points( + self.mesh_config, self.mesh_min, self.mesh_max, + self.radius, self.origin, probe_method + ) cmd_BED_MESH_CALIBRATE_help = "Perform Mesh Bed Leveling" def cmd_BED_MESH_CALIBRATE(self, gcmd): self._profile_name = gcmd.get('PROFILE', "default") if not self._profile_name.strip(): raise gcmd.error("Value for parameter 'PROFILE' must be specified") self.bedmesh.set_mesh(None) - self.update_config(gcmd) - self.probe_helper.start_probe(gcmd) + try: + self.update_config(gcmd) + except BedMeshError as e: + raise gcmd.error(str(e)) + self.probe_mgr.start_probe(gcmd) def probe_finalize(self, offsets, positions): x_offset, y_offset, z_offset = offsets positions = [[round(p[0], 2), round(p[1], 2), p[2]] for p in positions] - if self.zero_reference_mode == ZrefMode.PROBE: + if self.probe_mgr.get_zero_ref_mode() == ZrefMode.PROBE: ref_pos = positions.pop() logging.info( "bed_mesh: z-offset replaced with probed z value at " @@ -768,15 +618,17 @@ def probe_finalize(self, offsets, positions): x_cnt = params['x_count'] y_cnt = params['y_count'] - if self.substituted_indices: + substitutes = self.probe_mgr.get_substitutes() + base_points = self.probe_mgr.get_base_points() + if substitutes: # Replace substituted points with the original generated # point. Its Z Value is the average probed Z of the # substituted points. corrected_pts = [] idx_offset = 0 start_idx = 0 - for i, pts in self.substituted_indices.items(): - fpt = [p - o for p, o in zip(self.points[i], offsets[:2])] + for i, pts in substitutes.items(): + fpt = [p - o for p, o in zip(base_points[i], offsets[:2])] # offset the index to account for additional samples idx = i + idx_offset # Add "normal" points @@ -793,13 +645,13 @@ def probe_finalize(self, offsets, positions): corrected_pts.append(fpt) corrected_pts.extend(positions[start_idx:]) # validate corrected positions - if len(self.points) != len(corrected_pts): + if len(base_points) != len(corrected_pts): self._dump_points(positions, corrected_pts, offsets) raise self.gcode.error( "bed_mesh: invalid position list size, " "generated count: %d, probed count: %d" - % (len(self.points), len(corrected_pts))) - for gen_pt, probed in zip(self.points, corrected_pts): + % (len(base_points), len(corrected_pts))) + for gen_pt, probed in zip(base_points, corrected_pts): off_pt = [p - o for p, o in zip(gen_pt, offsets[:2])] if not isclose(off_pt[0], probed[0], abs_tol=.1) or \ not isclose(off_pt[1], probed[1], abs_tol=.1): @@ -866,11 +718,12 @@ def probe_finalize(self, offsets, positions): z_mesh.build_mesh(probed_matrix) except BedMeshError as e: raise self.gcode.error(str(e)) - if self.zero_reference_mode == ZrefMode.IN_MESH: + if self.probe_mgr.get_zero_ref_mode() == ZrefMode.IN_MESH: # The reference can be anywhere in the mesh, therefore # it is necessary to set the reference after the initial mesh # is generated to lookup the correct z value. - z_mesh.set_zero_reference(*self.zero_ref_pos) + zero_ref_pos = self.probe_mgr.get_zero_ref_pos() + z_mesh.set_zero_reference(*zero_ref_pos) self.bedmesh.set_mesh(z_mesh) self.gcode.respond_info("Mesh Bed Leveling Complete") if self._profile_name is not None: @@ -878,14 +731,15 @@ def probe_finalize(self, offsets, positions): def _dump_points(self, probed_pts, corrected_pts, offsets): # logs generated points with offset applied, points received # from the finalize callback, and the list of corrected points - max_len = max([len(self.points), len(probed_pts), len(corrected_pts)]) + points = self.probe_mgr.get_base_points() + max_len = max([len(points), len(probed_pts), len(corrected_pts)]) logging.info( "bed_mesh: calibration point dump\nIndex | %-17s| %-25s|" " Corrected Point" % ("Generated Point", "Probed Point")) for i in list(range(max_len)): gen_pt = probed_pt = corr_pt = "" - if i < len(self.points): - off_pt = [p - o for p, o in zip(self.points[i], offsets[:2])] + if i < len(points): + off_pt = [p - o for p, o in zip(points[i], offsets[:2])] gen_pt = "(%.2f, %.2f)" % tuple(off_pt) if i < len(probed_pts): probed_pt = "(%.2f, %.2f, %.4f)" % tuple(probed_pts[i]) @@ -894,6 +748,453 @@ def _dump_points(self, probed_pts, corrected_pts, offsets): logging.info( " %-4d| %-17s| %-25s| %s" % (i, gen_pt, probed_pt, corr_pt)) +class ProbeManager: + def __init__(self, config, orig_config, finalize_cb): + self.printer = config.get_printer() + self.cfg_overshoot = config.getfloat("scan_overshoot", 0, minval=1.) + self.orig_config = orig_config + self.faulty_regions = [] + self.overshoot = self.cfg_overshoot + self.zero_ref_pos = config.getfloatlist( + "zero_reference_position", None, count=2 + ) + self.zref_mode = ZrefMode.DISABLED + self.base_points = [] + self.substitutes = collections.OrderedDict() + self.is_round = orig_config["radius"] is not None + self.probe_helper = probe.ProbePointsHelper(config, finalize_cb, []) + self.probe_helper.use_xy_offsets(True) + self.rapid_scan_helper = RapidScanHelper(config, self, finalize_cb) + self._init_faulty_regions(config) + + def _init_faulty_regions(self, config): + for i in list(range(1, 100, 1)): + start = config.getfloatlist("faulty_region_%d_min" % (i,), None, + count=2) + if start is None: + break + end = config.getfloatlist("faulty_region_%d_max" % (i,), count=2) + # Validate the corners. If necessary reorganize them. + # c1 = min point, c3 = max point + # c4 ---- c3 + # | | + # c1 ---- c2 + c1 = [min([s, e]) for s, e in zip(start, end)] + c3 = [max([s, e]) for s, e in zip(start, end)] + c2 = [c1[0], c3[1]] + c4 = [c3[0], c1[1]] + # Check for overlapping regions + for j, (prev_c1, prev_c3) in enumerate(self.faulty_regions): + prev_c2 = [prev_c1[0], prev_c3[1]] + prev_c4 = [prev_c3[0], prev_c1[1]] + # Validate that no existing corner is within the new region + for coord in [prev_c1, prev_c2, prev_c3, prev_c4]: + if within(coord, c1, c3): + raise config.error( + "bed_mesh: Existing faulty_region_%d %s overlaps " + "added faulty_region_%d %s" + % (j+1, repr([prev_c1, prev_c3]), + i, repr([c1, c3]))) + # Validate that no new corner is within an existing region + for coord in [c1, c2, c3, c4]: + if within(coord, prev_c1, prev_c3): + raise config.error( + "bed_mesh: Added faulty_region_%d %s overlaps " + "existing faulty_region_%d %s" + % (i, repr([c1, c3]), + j+1, repr([prev_c1, prev_c3]))) + self.faulty_regions.append((c1, c3)) + + def start_probe(self, gcmd): + method = gcmd.get("METHOD", "automatic").lower() + can_scan = False + pprobe = self.printer.lookup_object("probe", None) + if pprobe is not None: + probe_name = pprobe.get_status(None).get("name", "") + can_scan = probe_name.startswith("probe_eddy_current") + if method == "rapid_scan" and can_scan: + self.rapid_scan_helper.perform_rapid_scan(gcmd) + else: + self.probe_helper.start_probe(gcmd) + + def get_zero_ref_pos(self): + return self.zero_ref_pos + + def get_zero_ref_mode(self): + return self.zref_mode + + def get_substitutes(self): + return self.substitutes + + def generate_points( + self, mesh_config, mesh_min, mesh_max, radius, origin, + probe_method="automatic" + ): + x_cnt = mesh_config['x_count'] + y_cnt = mesh_config['y_count'] + min_x, min_y = mesh_min + max_x, max_y = mesh_max + x_dist = (max_x - min_x) / (x_cnt - 1) + y_dist = (max_y - min_y) / (y_cnt - 1) + # floor distances down to next hundredth + x_dist = math.floor(x_dist * 100) / 100 + y_dist = math.floor(y_dist * 100) / 100 + if x_dist < 1. or y_dist < 1.: + raise BedMeshError("bed_mesh: min/max points too close together") + + if radius is not None: + # round bed, min/max needs to be recalculated + y_dist = x_dist + new_r = (x_cnt // 2) * x_dist + min_x = min_y = -new_r + max_x = max_y = new_r + else: + # rectangular bed, only re-calc max_x + max_x = min_x + x_dist * (x_cnt - 1) + pos_y = min_y + points = [] + for i in range(y_cnt): + for j in range(x_cnt): + if not i % 2: + # move in positive directon + pos_x = min_x + j * x_dist + else: + # move in negative direction + pos_x = max_x - j * x_dist + if radius is None: + # rectangular bed, append + points.append((pos_x, pos_y)) + else: + # round bed, check distance from origin + dist_from_origin = math.sqrt(pos_x*pos_x + pos_y*pos_y) + if dist_from_origin <= radius: + points.append( + (origin[0] + pos_x, origin[1] + pos_y)) + pos_y += y_dist + if self.zero_ref_pos is None or probe_method == "manual": + # Zero Reference Disabled + self.zref_mode = ZrefMode.DISABLED + elif within(self.zero_ref_pos, mesh_min, mesh_max): + # Zero Reference position within mesh + self.zref_mode = ZrefMode.IN_MESH + else: + # Zero Reference position outside of mesh + self.zref_mode = ZrefMode.PROBE + self.base_points = points + self.substitutes.clear() + # adjust overshoot + og_min_x = self.orig_config["mesh_min"][0] + og_max_x = self.orig_config["mesh_max"][0] + add_ovs = min(max(0, min_x - og_min_x), max(0, og_max_x - max_x)) + self.overshoot = self.cfg_overshoot + math.floor(add_ovs) + min_pt, max_pt = (min_x, min_y), (max_x, max_y) + self._process_faulty_regions(min_pt, max_pt, radius) + self.probe_helper.update_probe_points(self.get_std_path(), 3) + + def _process_faulty_regions(self, min_pt, max_pt, radius): + if not self.faulty_regions: + return + # Cannot probe a reference within a faulty region + if self.zref_mode == ZrefMode.PROBE: + for min_c, max_c in self.faulty_regions: + if within(self.zero_ref_pos, min_c, max_c): + opt = "zero_reference_position" + raise BedMeshError( + "bed_mesh: Cannot probe zero reference position at " + "(%.2f, %.2f) as it is located within a faulty region." + " Check the value for option '%s'" + % (self.zero_ref_pos[0], self.zero_ref_pos[1], opt,) + ) + # Check to see if any points fall within faulty regions + last_y = self.base_points[0][1] + is_reversed = False + for i, coord in enumerate(self.base_points): + if not isclose(coord[1], last_y): + is_reversed = not is_reversed + last_y = coord[1] + adj_coords = [] + for min_c, max_c in self.faulty_regions: + if within(coord, min_c, max_c, tol=.00001): + # Point lies within a faulty region + adj_coords = [ + (min_c[0], coord[1]), (coord[0], min_c[1]), + (coord[0], max_c[1]), (max_c[0], coord[1])] + if is_reversed: + # Swap first and last points for zig-zag pattern + first = adj_coords[0] + adj_coords[0] = adj_coords[-1] + adj_coords[-1] = first + break + if not adj_coords: + # coord is not located within a faulty region + continue + valid_coords = [] + for ac in adj_coords: + # make sure that coordinates are within the mesh boundary + if radius is None: + if within(ac, min_pt, max_pt, .000001): + valid_coords.append(ac) + else: + dist_from_origin = math.sqrt(ac[0]*ac[0] + ac[1]*ac[1]) + if dist_from_origin <= radius: + valid_coords.append(ac) + if not valid_coords: + raise BedMeshError( + "bed_mesh: Unable to generate coordinates" + " for faulty region at index: %d" % (i) + ) + self.substitutes[i] = valid_coords + + def get_base_points(self): + return self.base_points + + def get_std_path(self): + path = [] + for idx, pt in enumerate(self.base_points): + if idx in self.substitutes: + for sub_pt in self.substitutes[idx]: + path.append(sub_pt) + else: + path.append(pt) + if self.zref_mode == ZrefMode.PROBE: + path.append(self.zero_ref_pos) + return path + + def iter_rapid_path(self): + ascnd_x = True + last_base_pt = last_mv_pt = self.base_points[0] + # Generate initial move point + if self.overshoot: + overshoot = min(8, self.overshoot) + last_mv_pt = (last_base_pt[0] - overshoot, last_base_pt[1]) + yield last_mv_pt, False + for idx, pt in enumerate(self.base_points): + # increasing Y indicates direction change + dir_change = not isclose(pt[1], last_base_pt[1], abs_tol=1e-6) + if idx in self.substitutes: + fp_gen = self._gen_faulty_path( + last_mv_pt, idx, ascnd_x, dir_change + ) + for sub_pt, is_smp in fp_gen: + yield sub_pt, is_smp + last_mv_pt = sub_pt + else: + if dir_change: + for dpt in self._gen_dir_change(last_mv_pt, pt, ascnd_x): + yield dpt, False + yield pt, True + last_mv_pt = pt + last_base_pt = pt + ascnd_x ^= dir_change + if self.zref_mode == ZrefMode.PROBE: + if self.overshoot: + ovs = min(4, self.overshoot) + ovs = ovs if ascnd_x else -ovs + yield (last_mv_pt[0] + ovs, last_mv_pt[1]), False + yield self.zero_ref_pos, True + + def _gen_faulty_path(self, last_pt, idx, ascnd_x, dir_change): + subs = self.substitutes[idx] + sub_cnt = len(subs) + if dir_change: + for dpt in self._gen_dir_change(last_pt, subs[0], ascnd_x): + yield dpt, False + if self.is_round: + # No faulty region path handling for round beds + for pt in subs: + yield pt, True + return + # Check to see if this is the first corner + first_corner = False + sorted_sub_idx = sorted(self.substitutes.keys()) + if sub_cnt == 2 and idx < len(sorted_sub_idx): + first_corner = sorted_sub_idx[idx] == idx + yield subs[0], True + if sub_cnt == 1: + return + last_pt, next_pt = subs[:2] + if sub_cnt == 2: + if first_corner or dir_change: + # horizontal move first + yield (next_pt[0], last_pt[1]), False + else: + yield (last_pt[0], next_pt[1]), False + yield next_pt, True + elif sub_cnt >= 3: + if dir_change: + # first move should be a vertical switch up. If overshoot + # is available, simulate another direction change. Otherwise + # move inward 2 mm, then up through the faulty region. + if self.overshoot: + for dpt in self._gen_dir_change(last_pt, next_pt, ascnd_x): + yield dpt, False + else: + shift = -2 if ascnd_x else 2 + yield (last_pt[0] + shift, last_pt[1]), False + yield (last_pt[0] + shift, next_pt[1]), False + yield next_pt, True + last_pt, next_pt = subs[1:3] + else: + # vertical move + yield (last_pt[0], next_pt[1]), False + yield next_pt, True + last_pt, next_pt = subs[1:3] + if sub_cnt == 4: + # Vertical switch up within faulty region + shift = 2 if ascnd_x else -2 + yield (last_pt[0] + shift, last_pt[1]), False + yield (next_pt[0] - shift, next_pt[1]), False + yield next_pt, True + last_pt, next_pt = subs[2:4] + # horizontal move before final point + yield (next_pt[0], last_pt[1]), False + yield next_pt, True + + def _gen_dir_change(self, last_pt, next_pt, ascnd_x): + if not self.overshoot: + return + # overshoot X beyond the outer point + xdir = 1 if ascnd_x else -1 + overshoot = 2. if self.overshoot >= 3. else self.overshoot + ovr_pt = (last_pt[0] + overshoot * xdir, last_pt[1]) + yield ovr_pt + if self.overshoot < 3.: + # No room to generate an arc, move up to next y + yield (next_pt[0] + overshoot * xdir, next_pt[1]) + else: + # generate arc + STEP_ANGLE = 3 + START_ANGLE = 270 + ydiff = abs(next_pt[1] - last_pt[1]) + xdiff = abs(next_pt[0] - last_pt[0]) + max_radius = min(self.overshoot - 2, 8) + radius = min(ydiff / 2, max_radius) + origin = [ovr_pt[0], last_pt[1] + radius] + next_origin_y = next_pt[1] - radius + # determine angle + if xdiff < .01: + # Move is aligned on the x-axis + angle = 90 + if next_origin_y - origin[1] < .05: + # The move can be completed in a single arc + angle = 180 + else: + angle = int(math.degrees(math.atan(ydiff / xdiff))) + if ( + (ascnd_x and next_pt[0] < last_pt[0]) or + (not ascnd_x and next_pt[0] > last_pt[0]) + ): + angle = 180 - angle + count = int(angle // STEP_ANGLE) + # Gen first arc + step = STEP_ANGLE * xdir + start = START_ANGLE + step + for arc_pt in self._gen_arc(origin, radius, start, step, count): + yield arc_pt + if angle == 180: + # arc complete + return + # generate next arc + origin = [next_pt[0] + overshoot * xdir, next_origin_y] + # start at the angle where the last arc finished + start = START_ANGLE + count * step + # recalculate the count to make sure we generate a full 180 + # degrees. Add a step for the repeated connecting angle + count = 61 - count + for arc_pt in self._gen_arc(origin, radius, start, step, count): + yield arc_pt + + def _gen_arc(self, origin, radius, start, step, count): + end = start + step * count + # create a segent for every 3 degress of travel + for angle in range(start, end, step): + rad = math.radians(angle % 360) + opp = math.sin(rad) * radius + adj = math.cos(rad) * radius + yield (origin[0] + adj, origin[1] + opp) + + +MAX_HIT_DIST = 2. +MM_WIN_SPEED = 125 + +class RapidScanHelper: + def __init__(self, config, probe_mgr, finalize_cb): + self.printer = config.get_printer() + self.probe_manager = probe_mgr + self.speed = config.getfloat("speed", 50., above=0.) + self.scan_height = config.getfloat("horizontal_move_z", 5.) + self.finalize_callback = finalize_cb + + def perform_rapid_scan(self, gcmd): + speed = gcmd.get_float("SCAN_SPEED", self.speed) + scan_height = gcmd.get_float("HORIZONTAL_MOVE_Z", self.scan_height) + gcmd.respond_info( + "Beginning rapid surface scan at height %.2f..." % (scan_height) + ) + pprobe = self.printer.lookup_object("probe") + toolhead = self.printer.lookup_object("toolhead") + # Calculate time window around which a sample is valid. Current + # assumption is anything within 2mm is usable, so: + # window = 2 / max_speed + # + # TODO: validate maximum speed allowed based on sample rate of probe + # Scale the hit distance window for speeds lower than 125mm/s. The + # lower the speed the less the window shrinks. + scale = max(0, 1 - speed / MM_WIN_SPEED) + 1 + hit_dist = min(MAX_HIT_DIST, scale * speed / MM_WIN_SPEED) + half_window = hit_dist / speed + gcmd.respond_info( + "Sample hit distance +/- %.4fmm, time window +/- ms %.4f" + % (hit_dist, half_window * 1000) + ) + gcmd_params = gcmd.get_command_parameters() + gcmd_params["SAMPLE_TIME"] = half_window * 2 + self._raise_tool(gcmd, scan_height) + probe_session = pprobe.start_probe_session(gcmd) + offsets = pprobe.get_offsets() + initial_move = True + for pos, is_probe_pt in self.probe_manager.iter_rapid_path(): + pos = self._apply_offsets(pos[:2], offsets) + toolhead.manual_move(pos, speed) + if initial_move: + initial_move = False + self._move_to_scan_height(gcmd, scan_height) + if is_probe_pt: + probe_session.run_probe(gcmd) + results = probe_session.pull_probed_results() + toolhead.get_last_move_time() + self.finalize_callback(offsets, results) + probe_session.end_probe_session() + + def _raise_tool(self, gcmd, scan_height): + # If the nozzle is below scan height raise the tool + toolhead = self.printer.lookup_object("toolhead") + pprobe = self.printer.lookup_object("probe") + cur_pos = toolhead.get_position() + if cur_pos[2] >= scan_height: + return + pparams = pprobe.get_probe_params(gcmd) + lift_speed = pparams["lift_speed"] + cur_pos[2] = self.scan_height + .5 + toolhead.manual_move(cur_pos, lift_speed) + + def _move_to_scan_height(self, gcmd, scan_height): + time_window = gcmd.get_float("SAMPLE_TIME") + toolhead = self.printer.lookup_object("toolhead") + pprobe = self.printer.lookup_object("probe") + cur_pos = toolhead.get_position() + pparams = pprobe.get_probe_params(gcmd) + lift_speed = pparams["lift_speed"] + probe_speed = pparams["probe_speed"] + cur_pos[2] = scan_height + .5 + toolhead.manual_move(cur_pos, lift_speed) + cur_pos[2] = scan_height + toolhead.manual_move(cur_pos, probe_speed) + toolhead.dwell(time_window / 2 + .01) + + def _apply_offsets(self, point, offsets): + return [(pos - ofs) for pos, ofs in zip(point, offsets)] + class MoveSplitter: def __init__(self, config, gcode): From c7b7c11cc323e7e28521259e615f674990a1701e Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Wed, 14 Feb 2024 08:16:49 -0500 Subject: [PATCH 177/190] bed_mesh: add dump_mesh webhooks API Returns current mesh configuration and state. Includes probed and mesh matrices, saved profiles, current points, and travel paths. Signed-off-by: Eric Callahan --- klippy/extras/bed_mesh.py | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index d9ee7dfba8a6..8aa4958d51ae 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -121,6 +121,11 @@ def __init__(self, config): self.gcode.register_command( 'BED_MESH_OFFSET', self.cmd_BED_MESH_OFFSET, desc=self.cmd_BED_MESH_OFFSET_help) + # Register dump webhooks + webhooks = self.printer.lookup_object('webhooks') + webhooks.register_endpoint( + "bed_mesh/dump_mesh", self._handle_dump_request + ) # Register transform gcode_move = self.printer.load_object(config, 'gcode_move') gcode_move.set_move_transform(self) @@ -282,6 +287,31 @@ def cmd_BED_MESH_OFFSET(self, gcmd): gcode_move.reset_last_position() else: gcmd.respond_info("No mesh loaded to offset") + def _handle_dump_request(self, web_request): + eventtime = self.printer.get_reactor().monotonic() + prb = self.printer.lookup_object("probe", None) + th_sts = self.printer.lookup_object("toolhead").get_status(eventtime) + result = {"current_mesh": {}, "profiles": self.pmgr.get_profiles()} + if self.z_mesh is not None: + result["current_mesh"] = { + "name": self.z_mesh.get_profile_name(), + "probed_matrix": self.z_mesh.get_probed_matrix(), + "mesh_matrix": self.z_mesh.get_mesh_matrix(), + "mesh_params": self.z_mesh.get_mesh_params() + } + mesh_args = web_request.get_dict("mesh_args", {}) + gcmd = None + if mesh_args: + gcmd = self.gcode.create_gcode_command("", "", mesh_args) + with self.gcode.get_mutex(): + result["calibration"] = self.bmc.dump_calibration(gcmd) + else: + result["calibration"] = self.bmc.dump_calibration() + offsets = [0, 0, 0] if prb is None else prb.get_offsets() + result["probe_offsets"] = offsets + result["axis_minimum"] = th_sts["axis_minimum"] + result["axis_maximum"] = th_sts["axis_maximum"] + web_request.send(result) class ZrefMode: @@ -587,6 +617,20 @@ def update_config(self, gcmd): self.mesh_config, self.mesh_min, self.mesh_max, self.radius, self.origin, probe_method ) + def dump_calibration(self, gcmd=None): + if gcmd is not None and gcmd.get_command_parameters(): + self.update_config(gcmd) + cfg = dict(self.mesh_config) + cfg["mesh_min"] = self.mesh_min + cfg["mesh_max"] = self.mesh_max + cfg["origin"] = self.origin + cfg["radius"] = self.radius + return { + "points": self.probe_mgr.get_base_points(), + "config": cfg, + "probe_path": self.probe_mgr.get_std_path(), + "rapid_path": list(self.probe_mgr.iter_rapid_path()) + } cmd_BED_MESH_CALIBRATE_help = "Perform Mesh Bed Leveling" def cmd_BED_MESH_CALIBRATE(self, gcmd): self._profile_name = gcmd.get('PROFILE', "default") From fc0f17b920ce0fd2dca90dce0bd7df4573aee568 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Mon, 19 Feb 2024 15:10:56 -0500 Subject: [PATCH 178/190] graph_mesh: script for mesh visualization and analysis Signed-off-by: Eric Callahan --- scripts/graph_mesh.py | 533 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 533 insertions(+) create mode 100755 scripts/graph_mesh.py diff --git a/scripts/graph_mesh.py b/scripts/graph_mesh.py new file mode 100755 index 000000000000..3a331e5d5dca --- /dev/null +++ b/scripts/graph_mesh.py @@ -0,0 +1,533 @@ +#!/usr/bin/env python3 +# Bed Mesh data plotting and analysis +# +# Copyright (C) 2024 Eric Callahan +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import argparse +import sys +import os +import stat +import errno +import time +import socket +import re +import json +import collections +import numpy as np +import matplotlib +import matplotlib.cm as cm +import matplotlib.pyplot as plt +import matplotlib.animation as ani + +MESH_DUMP_REQUEST = json.dumps( + {"id": 1, "method": "bed_mesh/dump_mesh"} +) + +def sock_error_exit(msg): + sys.stderr.write(msg + "\n") + sys.exit(-1) + +def webhook_socket_create(uds_filename): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + while 1: + try: + sock.connect(uds_filename) + except socket.error as e: + if e.errno == errno.ECONNREFUSED: + time.sleep(0.1) + continue + sock_error_exit( + "Unable to connect socket %s [%d,%s]" + % (uds_filename, e.errno, errno.errorcode[e.errno]) + ) + break + print("Connected") + return sock + +def process_message(msg): + try: + resp = json.loads(msg) + except json.JSONDecodeError: + return None + if resp.get("id", -1) != 1: + return None + if "error" in resp: + err = resp["error"].get("message", "Unknown") + sock_error_exit( + "Error requesting mesh dump: %s" % (err,) + ) + return resp["result"] + + +def request_from_unixsocket(unix_sock_name): + print("Connecting to Unix Socket File '%s'" % (unix_sock_name,)) + whsock = webhook_socket_create(unix_sock_name) + whsock.settimeout(1.) + # send mesh query + whsock.send(MESH_DUMP_REQUEST.encode() + b"\x03") + sock_data = b"" + end_time = time.monotonic() + 10.0 + try: + while time.monotonic() < end_time: + try: + data = whsock.recv(4096) + except TimeoutError: + pass + else: + if not data: + sock_error_exit("Socket closed before mesh received") + parts = data.split(b"\x03") + parts[0] = sock_data + parts[0] + sock_data = parts.pop() + for msg in parts: + result = process_message(msg) + if result is not None: + return result + time.sleep(.1) + finally: + whsock.close() + sock_error_exit("Mesh dump request timed out") + +def request_from_websocket(url): + print("Connecting to websocket url '%s'" % (url,)) + try: + from websockets.sync.client import connect + except ModuleNotFoundError: + sock_error_exit("Python module 'websockets' not installed.") + raise + with connect(url) as websocket: + websocket.send(MESH_DUMP_REQUEST) + end_time = time.monotonic() + 20.0 + while time.monotonic() < end_time: + try: + msg = websocket.recv(10.) + except TimeoutError: + continue + result = process_message(msg) + if result is not None: + return result + time.sleep(.1) + sock_error_exit("Mesh dump request timed out") + +def request_mesh_data(input_name): + url_match = re.match(r"((?:https?)|(?:wss?))://(.+)", input_name.lower()) + if url_match is None: + file_path = os.path.abspath(os.path.expanduser(input_name)) + if not os.path.exists(file_path): + sock_error_exit("Path '%s' does not exist" % (file_path,)) + st_res = os.stat(file_path) + if stat.S_ISSOCK(st_res.st_mode): + return request_from_unixsocket(file_path) + else: + print("Reading mesh data from json file '%s'" % (file_path,)) + with open(file_path, "r") as f: + return json.load(f) + scheme = url_match.group(1) + host = url_match.group(2).rstrip("/") + scheme = scheme.replace("http", "ws") + url = "%s://%s/klippysocket" % (scheme, host) + return request_from_websocket(url) + +class PathAnimation: + instance = None + def __init__(self, artist, x_travel, y_travel): + self.travel_artist = artist + self.x_travel = x_travel + self.y_travel = y_travel + fig = plt.gcf() + self.animation = ani.FuncAnimation( + fig=fig, func=self.update, frames=self.gen_path_position(), + cache_frame_data=False, interval=60 + ) + PathAnimation.instance = self + + def gen_path_position(self): + count = 1 + x_travel, y_travel = self.x_travel, self.y_travel + last_x, last_y = x_travel[0], y_travel[0] + yield count + for xpos, ypos in zip(x_travel[1:], y_travel[1:]): + count += 1 + if xpos == last_x or ypos == last_y: + yield count + last_x, last_y = xpos, ypos + + def update(self, frame): + x_travel, y_travel = self.x_travel, self.y_travel + self.travel_artist.set_xdata(x_travel[:frame]) + self.travel_artist.set_ydata(y_travel[:frame]) + return (self.travel_artist,) + + +def _gen_mesh_coords(min_c, max_c, count): + dist = (max_c - min_c) / (count - 1) + return [min_c + i * dist for i in range(count)] + +def _plot_path(travel_path, probed, diff, cmd_args): + x_travel, y_travel = np.array(travel_path).transpose() + x_probed, y_probed = np.array(probed).transpose() + plt.xlabel("X") + plt.ylabel("Y") + # plot travel + travel_line = plt.plot(x_travel, y_travel, "b-")[0] + # plot intermediate points + plt.plot(x_probed, y_probed, "k.") + # plot start point + plt.plot([x_travel[0]], [y_travel[0]], "g>") + # plot stop point + plt.plot([x_travel[-1]], [y_travel[-1]], "r*") + if diff: + diff_x, diff_y = np.array(diff).transpose() + plt.plot(diff_x, diff_y, "m.") + if cmd_args.animate and cmd_args.output is None: + PathAnimation(travel_line, x_travel, y_travel) + +def _format_mesh_data(matrix, params): + min_pt = (params["min_x"], params["min_y"]) + max_pt = (params["max_x"], params["max_y"]) + xvals = _gen_mesh_coords(min_pt[0], max_pt[0], len(matrix[0])) + yvals = _gen_mesh_coords(min_pt[1], max_pt[0], len(matrix)) + x, y = np.meshgrid(xvals, yvals) + z = np.array(matrix) + return x, y, z + +def _set_xy_limits(mesh_data, cmd_args): + if not cmd_args.scale_plot: + return + ax = plt.gca() + axis_min = mesh_data["axis_minimum"] + axis_max = mesh_data["axis_maximum"] + ax.set_xlim((axis_min[0], axis_max[0])) + ax.set_ylim((axis_min[1], axis_max[1])) + +def _plot_mesh(ax, matrix, params, cmap=cm.viridis, label=None): + x, y, z = _format_mesh_data(matrix, params) + surface = ax.plot_surface(x, y, z, cmap=cmap, label=label) + scale = max(abs(z.min()), abs(z.max())) * 3 + return surface, scale + +def plot_probe_points(mesh_data, cmd_args): + """Plot original generated points""" + calibration = mesh_data["calibration"] + x, y = np.array(calibration["points"]).transpose() + plt.title("Generated Probe Points") + plt.xlabel("X") + plt.ylabel("Y") + plt.plot(x, y, "b.") + _set_xy_limits(mesh_data, cmd_args) + +def plot_probe_path(mesh_data, cmd_args): + """Plot probe travel path""" + calibration = mesh_data["calibration"] + orig_pts = calibration["points"] + path_pts = calibration["probe_path"] + diff = [pt for pt in orig_pts if pt not in path_pts] + plt.title("Probe Travel Path") + _plot_path(path_pts, path_pts[1:-1], diff, cmd_args) + _set_xy_limits(mesh_data, cmd_args) + +def plot_rapid_path(mesh_data, cmd_args): + """Plot rapid scan travel path""" + calibration = mesh_data["calibration"] + orig_pts = calibration["points"] + rapid_pts = calibration["rapid_path"] + rapid_path = [pt[0] for pt in rapid_pts] + probed = [pt for pt, is_ppt in rapid_pts if is_ppt] + diff = [pt for pt in orig_pts if pt not in probed] + plt.title("Rapid Scan Travel Path") + _plot_path(rapid_path, probed, diff, cmd_args) + _set_xy_limits(mesh_data, cmd_args) + +def plot_probed_matrix(mesh_data, cmd_args): + """Plot probed Z values""" + ax = plt.subplot(projection="3d") + profile = cmd_args.profile_name + if profile is not None: + req_mesh = mesh_data["profiles"].get(profile) + if req_mesh is None: + raise Exception("Profile %s not found" % (profile,)) + matrix = req_mesh["points"] + name = profile + else: + req_mesh = mesh_data["current_mesh"] + if not req_mesh: + raise Exception("No current mesh data in dump") + matrix = req_mesh["probed_matrix"] + name = req_mesh["name"] + params = req_mesh["mesh_params"] + surface, scale = _plot_mesh(ax, matrix, params) + ax.set_title("Probed Mesh (%s)" % (name,)) + ax.set(zlim=(-scale, scale)) + plt.gcf().colorbar(surface, shrink=.75) + _set_xy_limits(mesh_data, cmd_args) + +def plot_mesh_matrix(mesh_data, cmd_args): + """Plot mesh Z values""" + ax = plt.subplot(projection="3d") + req_mesh = mesh_data["current_mesh"] + if not req_mesh: + raise Exception("No current mesh data in dump") + matrix = req_mesh["mesh_matrix"] + params = req_mesh["mesh_params"] + surface, scale = _plot_mesh(ax, matrix, params) + name = req_mesh["name"] + ax.set_title("Interpolated Mesh (%s)" % (name,)) + ax.set(zlim=(-scale, scale)) + plt.gcf().colorbar(surface, shrink=.75) + _set_xy_limits(mesh_data, cmd_args) + +def plot_overlay(mesh_data, cmd_args): + """Plots the current probed mesh overlaid with a profile""" + ax = plt.subplot(projection="3d") + # Plot Profile + profile = cmd_args.profile_name + if profile is None: + raise Exception("A profile must be specified to plot an overlay") + req_mesh = mesh_data["profiles"].get(profile) + if req_mesh is None: + raise Exception("Profile %s not found" % (profile,)) + matrix = req_mesh["points"] + params = req_mesh["mesh_params"] + prof_surf, prof_scale = _plot_mesh(ax, matrix, params, label=profile) + # Plot Current + req_mesh = mesh_data["current_mesh"] + if not req_mesh: + raise Exception("No current mesh data in dump") + matrix = req_mesh["probed_matrix"] + params = req_mesh["mesh_params"] + cur_name = req_mesh["name"] + cur_surf, cur_scale = _plot_mesh(ax, matrix, params, cm.inferno, cur_name) + ax.set_title("Probed Mesh Overlay") + scale = max(cur_scale, prof_scale) + ax.set(zlim=(-scale, scale)) + ax.legend(loc='best') + plt.gcf().colorbar(prof_surf, shrink=.75) + _set_xy_limits(mesh_data, cmd_args) + +def plot_delta(mesh_data, cmd_args): + """Plots the delta between current probed mesh and a profile""" + ax = plt.subplot(projection="3d") + # Plot Profile + profile = cmd_args.profile_name + if profile is None: + raise Exception("A profile must be specified to plot an overlay") + req_mesh = mesh_data["profiles"].get(profile) + if req_mesh is None: + raise Exception("Profile %s not found" % (profile,)) + prof_matix = req_mesh["points"] + prof_params = req_mesh["mesh_params"] + req_mesh = mesh_data["current_mesh"] + if not req_mesh: + raise Exception("No current mesh data in dump") + cur_matrix = req_mesh["probed_matrix"] + cur_params = req_mesh["mesh_params"] + cur_name = req_mesh["name"] + # validate that the params match + pfields = ("x_count", "y_count", "min_x", "max_x", "min_y", "max_y") + for field in pfields: + if abs(prof_params[field] - cur_params[field]) >= 1e-6: + raise Exception( + "Values for field %s do not match, cant plot deviation" + ) + delta = np.array(cur_matrix) - np.array(prof_matix) + surface, scale = _plot_mesh(ax, delta, cur_params) + ax.set(zlim=(-scale, scale)) + ax.set_title("Probed Mesh Delta (%s, %s)" % (cur_name, profile)) + _set_xy_limits(mesh_data, cmd_args) + + +PLOT_TYPES = { + "points": plot_probe_points, + "path": plot_probe_path, + "rapid": plot_rapid_path, + "probedz": plot_probed_matrix, + "meshz": plot_mesh_matrix, + "overlay": plot_overlay, + "delta": plot_delta, +} + +def print_types(cmd_args): + typelist = [ + "%-10s%s" % (name, func.__doc__) for name, func in PLOT_TYPES.items() + ] + print("\n".join(typelist)) + +def plot_mesh_data(cmd_args): + mesh_data = request_mesh_data(cmd_args.input) + if cmd_args.output is not None: + matplotlib.use("svg") + + fig = plt.figure() + plot_func = PLOT_TYPES[cmd_args.type] + plot_func(mesh_data, cmd_args) + fig.set_size_inches(10, 8) + fig.tight_layout() + if cmd_args.output is None: + plt.show() + else: + fig.savefig(cmd_args.output) + +def _check_path_unique(name, path): + path = np.array(path) + unique_pts, counts = np.unique(path, return_counts=True, axis=0) + for idx, count in enumerate(counts): + if count != 1: + coord = unique_pts[idx] + print( + " WARNING: Backtracking or duplicate found in %s path at %s, " + "this may be due to multiple samples in a faulty region." + % (name, coord) + ) + +def _analyze_mesh(name, mesh_axes): + print("\nAnalyzing Probed Mesh %s..." % (name,)) + x, y, z = mesh_axes + min_idx, max_idx = z.argmin(), z.argmax() + min_x, min_y = x.flatten()[min_idx], y.flatten()[min_idx] + max_x, max_y = x.flatten()[max_idx], y.flatten()[max_idx] + + print( + " Min Coord (%.2f, %.2f), Max Coord (%.2f, %.2f), " + "Probe Count: (%d, %d)" % + (x.min(), y.min(), x.max(), y.max(), len(z), len(z[0])) + ) + print( + " Mesh range: min %.4f (%.2f, %.2f), max %.4f (%.2f, %.2f)" + % (z.min(), min_x, min_y, z.max(), max_x, max_y) + ) + print(" Mean: %.4f, Standard Deviation: %.4f" % (z.mean(), z.std())) + +def _compare_mesh(name_a, name_b, mesh_a, mesh_b): + ax, ay, az = mesh_a + bx, by, bz = mesh_b + if not np.array_equal(ax, bx) or not np.array_equal(ay, by): + return + delta = az - bz + abs_max = max(abs(delta.max()), abs(delta.min())) + abs_mean = sum([abs(z) for z in delta.flatten()]) / len(delta.flatten()) + min_idx, max_idx = delta.argmin(), delta.argmax() + min_x, min_y = ax.flatten()[min_idx], ay.flatten()[min_idx] + max_x, max_y = ax.flatten()[max_idx], ay.flatten()[max_idx] + print(" Delta from %s to %s..." % (name_a, name_b)) + print( + " Range: min %.4f (%.2f, %.2f), max %.4f (%.2f, %.2f)\n" + " Mean: %.6f, Standard Deviation: %.6f\n" + " Absolute Max: %.6f, Absolute Mean: %.6f" + % (delta.min(), min_x, min_y, delta.max(), max_x, max_y, + delta.mean(), delta.std(), abs_max, abs_mean) + ) + +def analyze(cmd_args): + mesh_data = request_mesh_data(cmd_args.input) + print("Analyzing Travel Path...") + calibration = mesh_data["calibration"] + org_pts = calibration["points"] + probe_path = calibration["probe_path"] + rapid_path = calibration["rapid_path"] + rapid_points = [pt for pt, is_pt in rapid_path if is_pt] + rapid_moves = [pt[0] for pt in rapid_path] + print(" Original point count: %d" % (len(org_pts))) + print(" Probe path count: %d" % (len(probe_path))) + print(" Rapid scan sample count: %d" % (len(probe_path))) + print(" Rapid scan move count: %d" % (len(rapid_moves))) + if np.array_equal(rapid_points, probe_path): + print(" Rapid scan points match probe path points") + else: + diff = [pt for pt in rapid_points if pt not in probe_path] + print( + " ERROR: Rapid scan points do not match probe points\n" + "difference: %s" % (diff,) + ) + _check_path_unique("probe", probe_path) + _check_path_unique("rapid scan", rapid_moves) + req_mesh = mesh_data["current_mesh"] + formatted_data = collections.OrderedDict() + if req_mesh: + matrix = req_mesh["probed_matrix"] + params = req_mesh["mesh_params"] + name = req_mesh["name"] + formatted_data[name] = _format_mesh_data(matrix, params) + profiles = mesh_data["profiles"] + for prof_name, prof_data in profiles.items(): + if prof_name in formatted_data: + continue + matrix = prof_data["points"] + params = prof_data["mesh_params"] + formatted_data[prof_name] = _format_mesh_data(matrix, params) + while formatted_data: + name, current_axes = formatted_data.popitem() + _analyze_mesh(name, current_axes) + for prof_name, prof_axes in formatted_data.items(): + _compare_mesh(name, prof_name, current_axes, prof_axes) + +def dump_request(cmd_args): + mesh_data = request_mesh_data(cmd_args.input) + outfile = cmd_args.output + if outfile is None: + postfix = time.strftime("%Y%m%d_%H%M%S") + outfile = "klipper-bedmesh-%s.json" % (postfix,) + outfile = os.path.abspath(os.path.expanduser(outfile)) + print("Saving Mesh Output to '%s'" % (outfile)) + with open(outfile, "w") as f: + f.write(json.dumps(mesh_data)) + +def main(): + parser = argparse.ArgumentParser(description="Graph Bed Mesh Data") + sub_parsers = parser.add_subparsers() + list_parser = sub_parsers.add_parser( + "list", help="List available plot types" + ) + list_parser.set_defaults(func=print_types) + plot_parser = sub_parsers.add_parser("plot", help="Plot a specified type") + analyze_parser = sub_parsers.add_parser( + "analyze", help="Perform analysis on mesh data" + ) + dump_parser = sub_parsers.add_parser( + "dump", help="Dump API response to json file" + ) + plot_parser.add_argument( + "-a", "--animate", action="store_true", + help="Animate paths in live preview" + ) + plot_parser.add_argument( + "-s", "--scale-plot", action="store_true", + help="Use axis limits reported by Klipper to scale plot X/Y" + ) + plot_parser.add_argument( + "-p", "--profile-name", type=str, default=None, + help="Optional name of a profile to plot for 'probedz'" + ) + plot_parser.add_argument( + "-o", "--output", type=str, default=None, + help="Output file path" + ) + plot_parser.add_argument( + "type", metavar="", type=str, choices=PLOT_TYPES.keys(), + help="Type of data to graph" + ) + plot_parser.add_argument( + "input", metavar="", + help="Path/url to Klipper Socket or path to json file" + ) + plot_parser.set_defaults(func=plot_mesh_data) + analyze_parser.add_argument( + "input", metavar="", + help="Path/url to Klipper Socket or path to json file" + ) + analyze_parser.set_defaults(func=analyze) + dump_parser.add_argument( + "-o", "--output", type=str, default=None, + help="Json output file path" + ) + dump_parser.add_argument( + "input", metavar="", + help="Path or url to Klipper Socket" + ) + dump_parser.set_defaults(func=dump_request) + cmd_args = parser.parse_args() + cmd_args.func(cmd_args) + + +if __name__ == "__main__": + main() From 2c7e09cfa6379e94fcc78774f186bce0bf0a2078 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Sun, 9 Jun 2024 19:21:01 -0400 Subject: [PATCH 179/190] bed_mesh: use generated XY positions in probe_finalize() The scan modes provide kinematic XYZ coordinates in the probe results. These positions may deviate from the requested positions, which can introduce errors in mesh generation when transposing the result into the Z matrix. Rely on the coordinates generated by bed mesh to transpose the matrix, presuming that points at the same index in the list match. Signed-off-by: Eric Callahan --- klippy/extras/bed_mesh.py | 57 +++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index 8aa4958d51ae..9c44692c35f0 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -643,7 +643,7 @@ def cmd_BED_MESH_CALIBRATE(self, gcmd): raise gcmd.error(str(e)) self.probe_mgr.start_probe(gcmd) def probe_finalize(self, offsets, positions): - x_offset, y_offset, z_offset = offsets + z_offset = offsets[2] positions = [[round(p[0], 2), round(p[1], 2), p[2]] for p in positions] if self.probe_mgr.get_zero_ref_mode() == ZrefMode.PROBE: @@ -654,16 +654,17 @@ def probe_finalize(self, offsets, positions): % (ref_pos[0], ref_pos[1], ref_pos[2]) ) z_offset = ref_pos[2] + base_points = self.probe_mgr.get_base_points() params = dict(self.mesh_config) - params['min_x'] = min(positions, key=lambda p: p[0])[0] + x_offset - params['max_x'] = max(positions, key=lambda p: p[0])[0] + x_offset - params['min_y'] = min(positions, key=lambda p: p[1])[1] + y_offset - params['max_y'] = max(positions, key=lambda p: p[1])[1] + y_offset + params['min_x'] = min(base_points, key=lambda p: p[0])[0] + params['max_x'] = max(base_points, key=lambda p: p[0])[0] + params['min_y'] = min(base_points, key=lambda p: p[1])[1] + params['max_y'] = max(base_points, key=lambda p: p[1])[1] x_cnt = params['x_count'] y_cnt = params['y_count'] substitutes = self.probe_mgr.get_substitutes() - base_points = self.probe_mgr.get_base_points() + probed_pts = positions if substitutes: # Replace substituted points with the original generated # point. Its Z Value is the average probed Z of the @@ -688,38 +689,42 @@ def probe_finalize(self, offsets, positions): % (i, fpt[0], fpt[1], avg_z, avg_z - z_offset)) corrected_pts.append(fpt) corrected_pts.extend(positions[start_idx:]) - # validate corrected positions - if len(base_points) != len(corrected_pts): - self._dump_points(positions, corrected_pts, offsets) - raise self.gcode.error( - "bed_mesh: invalid position list size, " - "generated count: %d, probed count: %d" - % (len(base_points), len(corrected_pts))) - for gen_pt, probed in zip(base_points, corrected_pts): - off_pt = [p - o for p, o in zip(gen_pt, offsets[:2])] - if not isclose(off_pt[0], probed[0], abs_tol=.1) or \ - not isclose(off_pt[1], probed[1], abs_tol=.1): - self._dump_points(positions, corrected_pts, offsets) - raise self.gcode.error( - "bed_mesh: point mismatch, orig = (%.2f, %.2f)" - ", probed = (%.2f, %.2f)" - % (off_pt[0], off_pt[1], probed[0], probed[1])) positions = corrected_pts + # validate length of result + if len(base_points) != len(positions): + self._dump_points(probed_pts, positions, offsets) + raise self.gcode.error( + "bed_mesh: invalid position list size, " + "generated count: %d, probed count: %d" + % (len(base_points), len(positions)) + ) + probed_matrix = [] row = [] - prev_pos = positions[0] - for pos in positions: + prev_pos = base_points[0] + for pos, result in zip(base_points, positions): + offset_pos = [p - o for p, o in zip(pos, offsets[:2])] + if ( + not isclose(offset_pos[0], result[0], abs_tol=.5) or + not isclose(offset_pos[1], result[1], abs_tol=.5) + ): + logging.info( + "bed_mesh: point deviation > .5mm: orig pt = (%.2f, %.2f)" + ", probed pt = (%.2f, %.2f)" + % (offset_pos[0], offset_pos[1], result[0], result[1]) + ) + z_pos = result[2] - z_offset if not isclose(pos[1], prev_pos[1], abs_tol=.1): # y has changed, append row and start new probed_matrix.append(row) row = [] if pos[0] > prev_pos[0]: # probed in the positive direction - row.append(pos[2] - z_offset) + row.append(z_pos) else: # probed in the negative direction - row.insert(0, pos[2] - z_offset) + row.insert(0, z_pos) prev_pos = pos # append last row probed_matrix.append(row) From a19af088945fdd33e49477413b90ee061c716e71 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Fri, 16 Feb 2024 06:13:05 -0500 Subject: [PATCH 180/190] bed_mesh: add support for MESH_PPS param in BMC In addition, do not respond with generated points. Signed-off-by: Eric Callahan --- klippy/extras/bed_mesh.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index 9c44692c35f0..bcfd7c74ded1 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -594,6 +594,12 @@ def update_config(self, gcmd): self.mesh_config['y_count'] = y_cnt need_cfg_update = True + if "MESH_PPS" in params: + xpps, ypps = parse_gcmd_pair(gcmd, 'MESH_PPS', minval=0) + self.mesh_config['mesh_x_pps'] = xpps + self.mesh_config['mesh_y_pps'] = ypps + need_cfg_update = True + if "ALGORITHM" in params: self.mesh_config['algo'] = gcmd.get('ALGORITHM').strip().lower() need_cfg_update = True From a19d64febdbaa7d5ec0a0912bb65c6547c370ec3 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Tue, 11 Jun 2024 14:31:39 -0400 Subject: [PATCH 181/190] docs: add rapid probing documentation Signed-off-by: Eric Callahan --- docs/API_Server.md | 127 ++++++++++++++++++ docs/Bed_Mesh.md | 270 ++++++++++++++++++++++++++++++++++++++- docs/Config_Reference.md | 7 + 3 files changed, 399 insertions(+), 5 deletions(-) diff --git a/docs/API_Server.md b/docs/API_Server.md index 4af1812a3a1d..cc0922e3ca81 100644 --- a/docs/API_Server.md +++ b/docs/API_Server.md @@ -401,3 +401,130 @@ might return: As with the "gcode/script" endpoint, this endpoint only completes after any pending G-Code commands complete. + +### bed_mesh/dump_mesh + +Dumps the configuration and state for the current mesh and all +saved profiles. + +For example: +`{"id": 123, "method": "bed_mesh/dump_mesh"}` + +might return: + +``` +{ + "current_mesh": { + "name": "eddy-scan-test", + "probed_matrix": [...], + "mesh_matrix": [...], + "mesh_params": { + "x_count": 9, + "y_count": 9, + "mesh_x_pps": 2, + "mesh_y_pps": 2, + "algo": "bicubic", + "tension": 0.5, + "min_x": 20, + "max_x": 330, + "min_y": 30, + "max_y": 320 + } + }, + "profiles": { + "default": { + "points": [...], + "mesh_params": { + "min_x": 20, + "max_x": 330, + "min_y": 30, + "max_y": 320, + "x_count": 9, + "y_count": 9, + "mesh_x_pps": 2, + "mesh_y_pps": 2, + "algo": "bicubic", + "tension": 0.5 + } + }, + "eddy-scan-test": { + "points": [...], + "mesh_params": { + "x_count": 9, + "y_count": 9, + "mesh_x_pps": 2, + "mesh_y_pps": 2, + "algo": "bicubic", + "tension": 0.5, + "min_x": 20, + "max_x": 330, + "min_y": 30, + "max_y": 320 + } + }, + "eddy-rapid-test": { + "points": [...], + "mesh_params": { + "x_count": 9, + "y_count": 9, + "mesh_x_pps": 2, + "mesh_y_pps": 2, + "algo": "bicubic", + "tension": 0.5, + "min_x": 20, + "max_x": 330, + "min_y": 30, + "max_y": 320 + } + } + }, + "calibration": { + "points": [...], + "config": { + "x_count": 9, + "y_count": 9, + "mesh_x_pps": 2, + "mesh_y_pps": 2, + "algo": "bicubic", + "tension": 0.5, + "mesh_min": [ + 20, + 30 + ], + "mesh_max": [ + 330, + 320 + ], + "origin": null, + "radius": null + }, + "probe_path": [...], + "rapid_path": [...] + }, + "probe_offsets": [ + 0, + 25, + 0.5 + ], + "axis_minimum": [ + 0, + 0, + -5, + 0 + ], + "axis_maximum": [ + 351, + 358, + 330, + 0 + ] +} +``` + +The `dump_mesh` endpoint takes one optional parameter, `mesh_args`. +This parameter must be an object, where the keys and values are +parameters available to [BED_MESH_CALIBRATE](#bed_mesh_calibrate). +This will update the mesh configuration and probe points using the +supplied parameters prior to returning the result. It is recommended +to omit mesh parameters unless it is desired to visualize the probe points +and/or travel path before performing `BED_MESH_CALIBRATE`. diff --git a/docs/Bed_Mesh.md b/docs/Bed_Mesh.md index 1538f6257210..62f1dee84ed7 100644 --- a/docs/Bed_Mesh.md +++ b/docs/Bed_Mesh.md @@ -421,12 +421,75 @@ have undesirable results when attempting print moves **outside** of the probed a full bed mesh has a variance greater than 1 layer height, caution must be taken when using adaptive bed meshes and attempting print moves outside of the meshed area. +## Surface Scans + +Some probes, such as the [Eddy Current Probe](./Eddy_Probe.md), are capable of +"scanning" the surface of the bed. That is, these probes can sample a mesh +without lifting the tool between samples. To activate scanning mode, the +`METHOD=scan` or `METHOD=rapid_scan` probe parameter should be passed in the +`BED_MESH_CALIBRATE` gcode command. + +### Scan Height + +The scan height is set by the `horizontal_move_z` option in `[bed_mesh]`. In +addition it can be supplied with the `BED_MESH_CALIBRATE` gcode command via the +`HORIZONTAL_MOVE_Z` parameter. + +The scan height must be sufficiently low to avoid scanning errors. Typically +a height of 2mm (ie: `HORIZONTAL_MOVE_Z=2`) should work well, presuming that the +probe is mounted correctly. + +It should be noted that if the probe is more than 4mm above the surface then the +results will be invalid. Thus, scanning is not possible on beds with severe +surface deviation or beds with extreme tilt that hasn't been corrected. + +### Rapid (Continuous) Scanning + +When performing a `rapid_scan` one should keep in mind that the results will +have some amount of error. This error should be low enough to be useful on +large print areas with reasonably thick layer heights. Some probes may be +more prone to error than others. + +It is not recommended that rapid mode be used to scan a "dense" mesh. Some of +the error introduced during a rapid scan may be gaussian noise from the sensor, +and a dense mesh will reflect this noise (ie: there will be peaks and valleys). + +Bed Mesh will attempt to optimize the travel path to provide the best possible +result based on the configuration. This includes avoiding faulty regions +when collecting samples and "overshooting" the mesh when changing direction. +This overshoot improves sampling at the edges of a mesh, however it requires +that the mesh be configured in a way that allows the tool to travel outside +of the mesh. + +``` +[bed_mesh] +speed: 120 +horizontal_move_z: 5 +mesh_min: 35, 6 +mesh_max: 240, 198 +probe_count: 5 +scan_overshoot: 8 +``` + +- `scan_overshoot` + _Default Value: 0 (disabled)_\ + The maximum amount of travel (in mm) available outside of the mesh. + For rectangular beds this applies to travel on the X axis, and for round beds + it applies to the entire radius. The tool must be able to travel the amount + specified outside of the mesh. This value is used to optimize the travel + path when performing a "rapid scan". The minimum value that may be specified + is 1. The default is no overshoot. + +If no scan overshoot is configured then travel path optimization will not +be applied to changes in direction. + ## Bed Mesh Gcodes ### Calibration -`BED_MESH_CALIBRATE PROFILE= METHOD=[manual | automatic] [=] - [=] [ADAPTIVE=[0|1] [ADAPTIVE_MARGIN=]`\ +`BED_MESH_CALIBRATE PROFILE= METHOD=[manual | automatic | scan | rapid_scan] \ +[=] [=] [ADAPTIVE=[0|1] \ +[ADAPTIVE_MARGIN=]`\ _Default Profile: default_\ _Default Method: automatic if a probe is detected, otherwise manual_ \ _Default Adaptive: 0_ \ @@ -435,9 +498,17 @@ _Default Adaptive Margin: 0_ Initiates the probing procedure for Bed Mesh Calibration. The mesh will be saved into a profile specified by the `PROFILE` parameter, -or `default` if unspecified. If `METHOD=manual` is selected then manual probing -will occur. When switching between automatic and manual probing the generated -mesh points will automatically be adjusted. +or `default` if unspecified. The `METHOD` parameter takes one of the following +values: + +- `METHOD=manual`: enables manual probing using the nozzle and the paper test +- `METHOD=automatic`: Automatic (standard) probing. This is the default. +- `METHOD=scan`: Enables surface scanning. The tool will pause over each position + to collect a sample. +- `METHOD=rapid_scan`: Enables continuous surface scanning. + +XY positions are automatically adjusted to include the X and/or Y offsets +when a probing method other than `manual` is selected. It is possible to specify mesh parameters to modify the probed area. The following parameters are available: @@ -451,6 +522,7 @@ following parameters are available: - `MESH_ORIGIN` - `ROUND_PROBE_COUNT` - All beds: + - `MESH_PPS` - `ALGORITHM` - `ADAPTIVE` - `ADAPTIVE_MARGIN` @@ -557,3 +629,191 @@ is intended to compensate for a `gcode offset` when [mesh fade](#mesh-fade) is enabled. For example, if a secondary extruder is higher than the primary and needs a negative gcode offset, ie: `SET_GCODE_OFFSET Z=-.2`, it can be accounted for in `bed_mesh` with `BED_MESH_OFFSET ZFADE=.2`. + +## Bed Mesh Webhooks APIs + +### Dumping mesh data + +`{"id": 123, "method": "bed_mesh/dump_mesh"}` + +Dumps the configuration and state for the current mesh and all +saved profiles. + +The `dump_mesh` endpoint takes one optional parameter, `mesh_args`. +This parameter must be an object, where the keys and values are +parameters available to [BED_MESH_CALIBRATE](#bed_mesh_calibrate). +This will update the mesh configuration and probe points using the +supplied parameters prior to returning the result. It is recommended +to omit mesh parameters unless it is desired to visualize the probe points +and/or travel path before performing `BED_MESH_CALIBRATE`. + +## Visualization and analysis + +Most users will likely find that the visualizers included with +applications such as Mainsail, Fluidd, and Octoprint are sufficient +for basic analysis. However, Klipper's `scripts` folder contains the +`graph_mesh.py` script that may be used to perform additional +visualizations and more detailed analysis, particularly useful +for debugging hardware or the results produced by `bed_mesh`: + +``` +usage: graph_mesh.py [-h] {list,plot,analyze,dump} ... + +Graph Bed Mesh Data + +positional arguments: + {list,plot,analyze,dump} + list List available plot types + plot Plot a specified type + analyze Perform analysis on mesh data + dump Dump API response to json file + +options: + -h, --help show this help message and exit +``` + +### Pre-requisites + +Like most graphing tools provided by Klipper, `graph_mesh.py` requires +the `matplotlib` and `numpy` python dependencies. In addition, connecting +to Klipper via Moonraker's websocket requires the `websockets` python +dependency. While all visualizations can be output to an `svg` file, most of +the visualizations offered by `graph_mesh.py` are better viewed in live +preview mode on a desktop class PC. For example, the 3D visualizations may be +rotated and zoomed in preview mode, and the path visualizations can optionally +be animated in preview mode. + +### Plotting Mesh data + +The `graph_mesh.py` tool can plot several types of visualizations. +Available types can be shown by running `graph_mesh.py list`: + +``` +graph_mesh.py list +points Plot original generated points +path Plot probe travel path +rapid Plot rapid scan travel path +probedz Plot probed Z values +meshz Plot mesh Z values +overlay Plots the current probed mesh overlaid with a profile +delta Plots the delta between current probed mesh and a profile +``` + +Several options are available when plotting visualizations: + +``` +usage: graph_mesh.py plot [-h] [-a] [-s] [-p PROFILE_NAME] [-o OUTPUT] + +positional arguments: + Type of data to graph + Path/url to Klipper Socket or path to json file + +options: + -h, --help show this help message and exit + -a, --animate Animate paths in live preview + -s, --scale-plot Use axis limits reported by Klipper to scale plot X/Y + -p PROFILE_NAME, --profile-name PROFILE_NAME + Optional name of a profile to plot for 'probedz' + -o OUTPUT, --output OUTPUT + Output file path +``` + +Below is a description of each argument: + +- `plot type`: A required positional argument designating the type of + visualization to generate. Must be one of the types output by the + `graph_mesh.py list` command. +- `input`: A required positional argument containing a path or url + to the input source. This must be one of the following: + - A path to Klipper's Unix Domain Socket + - A url to an instance of Moonraker + - A path to a json file produced by `graph_mesh.py dump ` +- `-a`: Optional animation for the `path` and `rapid` visualization types. + Animations only apply to a live preview. +- `-s`: Optionally scales a plot using the `axis_minimum` and `axis_maximum` + values reported by Klipper's `toolhead` object when the dump file was + generated. +- `-p`: A profile name that may be specified when generating the + `probedz` 3D mesh visualization. When generating an `overlay` or + `delta` visualization this argument must be provided. +- `-o`: An optional file path indicating that the script should save the + visualization to this location rather than run in preview mode. Images + are saved in `svg` format. + +For example, to plot an animated rapid path, connecting via Klipper's unix +socket: + +``` +graph_mesh.py plot -a rapid ~/printer_data/comms/klippy.sock +``` + +Or to plot a 3d visualization of the mesh, connecting via Moonraker: + +``` +graph_mesh.py plot meshz http://my-printer.local +``` + +### Bed Mesh Analysis + +The `graph_mesh.py` tool may also be used to perform an analysis on the +data provided by the [bed_mesh/dump_mesh](#dumping-mesh-data) API: + +``` +graph_mesh.py analyze +``` + +As with the `plot` command, the `` must be a path to Klipper's +unix socket, a URL to an instance of Moonraker, or a path to a json file +generated by the dump command. + +To begin, the analysis will perform various checks on the points and +probe paths generated by `bed_mesh` at the time of the dump. This +includes the following: + +- The number of probe points generated, without any additions +- The number of probe points generated including any points generated + as the result faulty regions and/or a configured zero reference position. +- The number of probe points generated when performing a rapid scan. +- The total number of moves generated for a rapid scan. +- A validation that the probe points generated for a rapid scan are + identical to the probe points generated for a standard probing procedure. +- A "backtracking" check for both the standard probe path and a rapid scan + path. Backtracking can be defined as moving to the same position more than + once during the probing procedure. Backtracking should never occur during a + standard probe. Faulty regions *can* result in backtracking during a rapid + scan in an attempt to avoid entering a faulty region when approaching or + leaving a probe location, however should never occur otherwise. + +Next each probed mesh present in the dump will by analyzed, beginning with +the mesh loaded at the time of the dump (if present) and followed by any +saved profiles. The following data is extracted: + +- Mesh shape (Min X,Y, Max X,Y Probe Count) +- Mesh Z range, (Minimum Z, Maximum Z) +- Mean Z value in the mesh +- Standard Deviation of the Z values in the Mesh + +In addition to the above, a delta analysis is performed between meshes +with the same shape, reporting the following: +- The range of the delta between to meshes (Minimum and Maximum) +- The mean delta +- Standard Deviation of the delta +- The absolute maximum difference +- The absolute mean + +### Save mesh data to a file + +The `dump` command may be used to save the response to a file which +can be shared for analysis when troubleshooting: + +``` +graph_mesh.py dump -o +``` + +The `` should be a path to Klipper's unix socket or +a URL to an instance of Moonraker. The `-o` option may be used to +specify the path to the output file. If omitted, the file will be +saved in the working directory, with a file name in the following +format: + +`klipper-bedmesh-{year}{month}{day}{hour}{minute}{second}.json` diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 6b42fe48da91..b192e7362c76 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -998,6 +998,13 @@ Visual Examples: #adaptive_margin: # An optional margin (in mm) to be added around the bed area used by # the defined print objects when generating an adaptive mesh. +#scan_overshoot: +# The maximum amount of travel (in mm) available outside of the mesh. +# For rectangular beds this applies to travel on the X axis, and for round beds +# it applies to the entire radius. The tool must be able to travel the amount +# specified outside of the mesh. This value is used to optimize the travel +# path when performing a "rapid scan". The minimum value that may be specified +# is 1. The default is no overshoot. ``` ### [bed_tilt] From 4ac283cc0e198ad64aad8f321e4f158065828397 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 15 Jun 2024 12:13:35 -0400 Subject: [PATCH 182/190] error_mcu: Move shutdown error message formatting to new error_mcu.py module Create a new module to help format verbose mcu error messages. Move the shutdown message formatting to this module. This moves the error formatting out of the background thread and out of the critical shutdown code path. Signed-off-by: Kevin O'Connor --- klippy/extras/error_mcu.py | 67 ++++++++++++++++++++++++++++++++++++++ klippy/klippy.py | 25 +++++++------- klippy/mcu.py | 41 ++++------------------- 3 files changed, 87 insertions(+), 46 deletions(-) create mode 100644 klippy/extras/error_mcu.py diff --git a/klippy/extras/error_mcu.py b/klippy/extras/error_mcu.py new file mode 100644 index 000000000000..ad737f9a78cf --- /dev/null +++ b/klippy/extras/error_mcu.py @@ -0,0 +1,67 @@ +# More verbose information on micro-controller errors +# +# Copyright (C) 2024 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import logging + +message_shutdown = """ +Once the underlying issue is corrected, use the +"FIRMWARE_RESTART" command to reset the firmware, reload the +config, and restart the host software. +Printer is shutdown +""" + +Common_MCU_errors = { + ("Timer too close",): """ +This often indicates the host computer is overloaded. Check +for other processes consuming excessive CPU time, high swap +usage, disk errors, overheating, unstable voltage, or +similar system problems on the host computer.""", + ("Missed scheduling of next ",): """ +This is generally indicative of an intermittent +communication failure between micro-controller and host.""", + ("ADC out of range",): """ +This generally occurs when a heater temperature exceeds +its configured min_temp or max_temp.""", + ("Rescheduled timer in the past", "Stepper too far in past"): """ +This generally occurs when the micro-controller has been +requested to step at a rate higher than it is capable of +obtaining.""", + ("Command request",): """ +This generally occurs in response to an M112 G-Code command +or in response to an internal error in the host software.""", +} + +def error_hint(msg): + for prefixes, help_msg in Common_MCU_errors.items(): + for prefix in prefixes: + if msg.startswith(prefix): + return help_msg + return "" + +class PrinterMCUError: + def __init__(self, config): + self.printer = config.get_printer() + self.printer.register_event_handler("klippy:notify_mcu_shutdown", + self._handle_notify_mcu_shutdown) + def _check_mcu_shutdown(self, msg, details): + mcu_name = details['mcu'] + mcu_msg = details['reason'] + event_type = details['event_type'] + prefix = "MCU '%s' shutdown: " % (mcu_name,) + if event_type == 'is_shutdown': + prefix = "Previous MCU '%s' shutdown: " % (mcu_name,) + # Lookup generic hint + hint = error_hint(msg) + # Update error message + newmsg = "%s%s%s%s" % (prefix, mcu_msg, hint, message_shutdown) + self.printer.update_error_msg(msg, newmsg) + def _handle_notify_mcu_shutdown(self, msg, details): + if msg == "MCU shutdown": + self._check_mcu_shutdown(msg, details) + else: + self.printer.update_error_msg(msg, "%s%s" % (msg, message_shutdown)) + +def load_config(config): + return PrinterMCUError(config) diff --git a/klippy/klippy.py b/klippy/klippy.py index 097cff998c22..5574063dd34f 100644 --- a/klippy/klippy.py +++ b/klippy/klippy.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 # Main code for host side printer firmware # -# Copyright (C) 2016-2020 Kevin O'Connor +# Copyright (C) 2016-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import sys, os, gc, optparse, logging, time, collections, importlib @@ -40,13 +40,6 @@ Error configuring printer """ -message_shutdown = """ -Once the underlying issue is corrected, use the -"FIRMWARE_RESTART" command to reset the firmware, reload the -config, and restart the host software. -Printer is shutdown -""" - class Printer: config_error = configfile.error command_error = gcode.CommandError @@ -85,6 +78,13 @@ def _set_state(self, msg): if (msg != message_ready and self.start_args.get('debuginput') is not None): self.request_exit('error_exit') + def update_error_msg(self, oldmsg, newmsg): + if (self.state_message != oldmsg + or self.state_message in (message_ready, message_startup) + or newmsg in (message_ready, message_startup)): + return + self.state_message = newmsg + logging.error(newmsg) def add_object(self, name, obj): if name in self.objects: raise self.config_error( @@ -241,12 +241,12 @@ def set_rollover_info(self, name, info, log=True): logging.info(info) if self.bglogger is not None: self.bglogger.set_rollover_info(name, info) - def invoke_shutdown(self, msg): + def invoke_shutdown(self, msg, details={}): if self.in_shutdown_state: return logging.error("Transition to shutdown state: %s", msg) self.in_shutdown_state = True - self._set_state("%s%s" % (msg, message_shutdown)) + self._set_state(msg) for cb in self.event_handlers.get("klippy:shutdown", []): try: cb() @@ -254,9 +254,10 @@ def invoke_shutdown(self, msg): logging.exception("Exception during shutdown handler") logging.info("Reactor garbage collection: %s", self.reactor.get_gc_stats()) - def invoke_async_shutdown(self, msg): + self.send_event("klippy:notify_mcu_shutdown", msg, details) + def invoke_async_shutdown(self, msg, details): self.reactor.register_async_callback( - (lambda e: self.invoke_shutdown(msg))) + (lambda e: self.invoke_shutdown(msg, details))) def register_event_handler(self, event, callback): self.event_handlers.setdefault(event, []).append(callback) def send_event(self, event, *params): diff --git a/klippy/mcu.py b/klippy/mcu.py index 23ba07173257..feb4856a1d22 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -1,6 +1,6 @@ # Interface to Klipper micro-controller code # -# Copyright (C) 2016-2023 Kevin O'Connor +# Copyright (C) 2016-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import sys, os, zlib, logging, math @@ -605,6 +605,7 @@ def __init__(self, config, clocksync): self._mcu_tick_stddev = 0. self._mcu_tick_awake = 0. # Register handlers + printer.load_object(config, "error_mcu") printer.register_event_handler("klippy:firmware_restart", self._firmware_restart) printer.register_event_handler("klippy:mcu_identify", @@ -631,13 +632,13 @@ def _handle_shutdown(self, params): if clock is not None: self._shutdown_clock = self.clock32_to_clock64(clock) self._shutdown_msg = msg = params['static_string_id'] - logging.info("MCU '%s' %s: %s\n%s\n%s", self._name, params['#name'], + event_type = params['#name'] + self._printer.invoke_async_shutdown( + "MCU shutdown", {"reason": msg, "mcu": self._name, + "event_type": event_type}) + logging.info("MCU '%s' %s: %s\n%s\n%s", self._name, event_type, self._shutdown_msg, self._clocksync.dump_debug(), self._serial.dump_debug()) - prefix = "MCU '%s' shutdown: " % (self._name,) - if params['#name'] == 'is_shutdown': - prefix = "Previous MCU '%s' shutdown: " % (self._name,) - self._printer.invoke_async_shutdown(prefix + msg + error_help(msg)) def _handle_starting(self, params): if not self._is_shutdown: self._printer.invoke_async_shutdown("MCU '%s' spontaneous restart" @@ -1008,34 +1009,6 @@ def stats(self, eventtime): self._get_status_info['last_stats'] = last_stats return False, '%s: %s' % (self._name, stats) -Common_MCU_errors = { - ("Timer too close",): """ -This often indicates the host computer is overloaded. Check -for other processes consuming excessive CPU time, high swap -usage, disk errors, overheating, unstable voltage, or -similar system problems on the host computer.""", - ("Missed scheduling of next ",): """ -This is generally indicative of an intermittent -communication failure between micro-controller and host.""", - ("ADC out of range",): """ -This generally occurs when a heater temperature exceeds -its configured min_temp or max_temp.""", - ("Rescheduled timer in the past", "Stepper too far in past"): """ -This generally occurs when the micro-controller has been -requested to step at a rate higher than it is capable of -obtaining.""", - ("Command request",): """ -This generally occurs in response to an M112 G-Code command -or in response to an internal error in the host software.""", -} - -def error_help(msg): - for prefixes, help_msg in Common_MCU_errors.items(): - for prefix in prefixes: - if msg.startswith(prefix): - return help_msg - return "" - def add_printer_objects(config): printer = config.get_printer() reactor = printer.get_reactor() From ba529996ea18edf87de0bae41d8c458ffc07b889 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 15 Jun 2024 12:27:36 -0400 Subject: [PATCH 183/190] error_mcu: Move mcu protocol error reporting to error_mcu module Signed-off-by: Kevin O'Connor --- klippy/extras/error_mcu.py | 43 +++++++++++++++++++++++++++++++++++++ klippy/klippy.py | 44 ++++---------------------------------- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/klippy/extras/error_mcu.py b/klippy/extras/error_mcu.py index ad737f9a78cf..339eb73133d5 100644 --- a/klippy/extras/error_mcu.py +++ b/klippy/extras/error_mcu.py @@ -12,6 +12,17 @@ Printer is shutdown """ +message_protocol_error1 = """ +This is frequently caused by running an older version of the +firmware on the MCU(s). Fix by recompiling and flashing the +firmware. +""" + +message_protocol_error2 = """ +Once the underlying issue is corrected, use the "RESTART" +command to reload the config and restart the host software. +""" + Common_MCU_errors = { ("Timer too close",): """ This often indicates the host computer is overloaded. Check @@ -45,6 +56,8 @@ def __init__(self, config): self.printer = config.get_printer() self.printer.register_event_handler("klippy:notify_mcu_shutdown", self._handle_notify_mcu_shutdown) + self.printer.register_event_handler("klippy:notify_mcu_error", + self._handle_notify_mcu_error) def _check_mcu_shutdown(self, msg, details): mcu_name = details['mcu'] mcu_msg = details['reason'] @@ -62,6 +75,36 @@ def _handle_notify_mcu_shutdown(self, msg, details): self._check_mcu_shutdown(msg, details) else: self.printer.update_error_msg(msg, "%s%s" % (msg, message_shutdown)) + def _check_protocol_error(self, msg, details): + host_version = self.printer.start_args['software_version'] + msg_update = [] + msg_updated = [] + for mcu_name, mcu in self.printer.lookup_objects('mcu'): + try: + mcu_version = mcu.get_status()['mcu_version'] + except: + logging.exception("Unable to retrieve mcu_version from mcu") + continue + if mcu_version != host_version: + msg_update.append("%s: Current version %s" + % (mcu_name.split()[-1], mcu_version)) + else: + msg_updated.append("%s: Current version %s" + % (mcu_name.split()[-1], mcu_version)) + if not msg_update: + msg_update.append("") + if not msg_updated: + msg_updated.append("") + newmsg = ["MCU Protocol error", + message_protocol_error1, + "Your Klipper version is: %s" % (host_version,), + "MCU(s) which should be updated:"] + newmsg += msg_update + ["Up-to-date MCU(s):"] + msg_updated + newmsg += [message_protocol_error2, details['error']] + self.printer.update_error_msg(msg, "\n".join(newmsg)) + def _handle_notify_mcu_error(self, msg, details): + if msg == "Protocol error": + self._check_protocol_error(msg, details) def load_config(config): return PrinterMCUError(config) diff --git a/klippy/klippy.py b/klippy/klippy.py index 5574063dd34f..17609b37e522 100644 --- a/klippy/klippy.py +++ b/klippy/klippy.py @@ -22,17 +22,6 @@ Printer is halted """ -message_protocol_error1 = """ -This is frequently caused by running an older version of the -firmware on the MCU(s). Fix by recompiling and flashing the -firmware. -""" - -message_protocol_error2 = """ -Once the underlying issue is corrected, use the "RESTART" -command to reload the config and restart the host software. -""" - message_mcu_connect_error = """ Once the underlying issue is corrected, use the "FIRMWARE_RESTART" command to reset the firmware, reload the @@ -143,33 +132,6 @@ def _read_config(self): m.add_printer_objects(config) # Validate that there are no undefined parameters in the config file pconfig.check_unused_options(config) - def _build_protocol_error_message(self, e): - host_version = self.start_args['software_version'] - msg_update = [] - msg_updated = [] - for mcu_name, mcu in self.lookup_objects('mcu'): - try: - mcu_version = mcu.get_status()['mcu_version'] - except: - logging.exception("Unable to retrieve mcu_version from mcu") - continue - if mcu_version != host_version: - msg_update.append("%s: Current version %s" - % (mcu_name.split()[-1], mcu_version)) - else: - msg_updated.append("%s: Current version %s" - % (mcu_name.split()[-1], mcu_version)) - if not msg_update: - msg_update.append("") - if not msg_updated: - msg_updated.append("") - msg = ["MCU Protocol error", - message_protocol_error1, - "Your Klipper version is: %s" % (host_version,), - "MCU(s) which should be updated:"] - msg += msg_update + ["Up-to-date MCU(s):"] + msg_updated - msg += [message_protocol_error2, str(e)] - return "\n".join(msg) def _connect(self, eventtime): try: self._read_config() @@ -183,8 +145,10 @@ def _connect(self, eventtime): self._set_state("%s\n%s" % (str(e), message_restart)) return except msgproto.error as e: - logging.exception("Protocol error") - self._set_state(self._build_protocol_error_message(e)) + msg = "Protocol error" + logging.exception(msg) + self._set_state(msg) + self.send_event("klippy:notify_mcu_error", msg, {"error": str(e)}) util.dump_mcu_build() return except mcu.error as e: From 7149bb1b6de2616f48b40f060ad8c887cb6a84d2 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 15 Jun 2024 12:34:29 -0400 Subject: [PATCH 184/190] error_mcu: Move formatting of mcu connect errors to error_mcu module Signed-off-by: Kevin O'Connor --- klippy/extras/error_mcu.py | 12 ++++++++++++ klippy/klippy.py | 13 ++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/klippy/extras/error_mcu.py b/klippy/extras/error_mcu.py index 339eb73133d5..536debbb06e9 100644 --- a/klippy/extras/error_mcu.py +++ b/klippy/extras/error_mcu.py @@ -23,6 +23,13 @@ command to reload the config and restart the host software. """ +message_mcu_connect_error = """ +Once the underlying issue is corrected, use the +"FIRMWARE_RESTART" command to reset the firmware, reload the +config, and restart the host software. +Error configuring printer +""" + Common_MCU_errors = { ("Timer too close",): """ This often indicates the host computer is overloaded. Check @@ -102,9 +109,14 @@ def _check_protocol_error(self, msg, details): newmsg += msg_update + ["Up-to-date MCU(s):"] + msg_updated newmsg += [message_protocol_error2, details['error']] self.printer.update_error_msg(msg, "\n".join(newmsg)) + def _check_mcu_connect_error(self, msg, details): + newmsg = "%s%s" % (details['error'], message_mcu_connect_error) + self.printer.update_error_msg(msg, newmsg) def _handle_notify_mcu_error(self, msg, details): if msg == "Protocol error": self._check_protocol_error(msg, details) + elif msg == "MCU error during connect": + self._check_mcu_connect_error(msg, details) def load_config(config): return PrinterMCUError(config) diff --git a/klippy/klippy.py b/klippy/klippy.py index 17609b37e522..75ee6887ad71 100644 --- a/klippy/klippy.py +++ b/klippy/klippy.py @@ -22,13 +22,6 @@ Printer is halted """ -message_mcu_connect_error = """ -Once the underlying issue is corrected, use the -"FIRMWARE_RESTART" command to reset the firmware, reload the -config, and restart the host software. -Error configuring printer -""" - class Printer: config_error = configfile.error command_error = gcode.CommandError @@ -152,8 +145,10 @@ def _connect(self, eventtime): util.dump_mcu_build() return except mcu.error as e: - logging.exception("MCU error during connect") - self._set_state("%s%s" % (str(e), message_mcu_connect_error)) + msg = "MCU error during connect" + logging.exception(msg) + self._set_state(msg) + self.send_event("klippy:notify_mcu_error", msg, {"error": str(e)}) util.dump_mcu_build() return except Exception as e: From 9fa0fb1a0ebca3ed4be887417b255b26fc99bbfd Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 18 Jun 2024 12:51:32 -0400 Subject: [PATCH 185/190] error_mcu: Support mechanism to add per-instance context to a shutdown Signed-off-by: Kevin O'Connor --- klippy/extras/error_mcu.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/klippy/extras/error_mcu.py b/klippy/extras/error_mcu.py index 536debbb06e9..dc91c33a92c7 100644 --- a/klippy/extras/error_mcu.py +++ b/klippy/extras/error_mcu.py @@ -61,10 +61,13 @@ def error_hint(msg): class PrinterMCUError: def __init__(self, config): self.printer = config.get_printer() + self.clarify_callbacks = {} self.printer.register_event_handler("klippy:notify_mcu_shutdown", self._handle_notify_mcu_shutdown) self.printer.register_event_handler("klippy:notify_mcu_error", self._handle_notify_mcu_error) + def add_clarify(self, msg, callback): + self.clarify_callbacks.setdefault(msg, []).append(callback) def _check_mcu_shutdown(self, msg, details): mcu_name = details['mcu'] mcu_msg = details['reason'] @@ -73,9 +76,17 @@ def _check_mcu_shutdown(self, msg, details): if event_type == 'is_shutdown': prefix = "Previous MCU '%s' shutdown: " % (mcu_name,) # Lookup generic hint - hint = error_hint(msg) + hint = error_hint(mcu_msg) + # Add per instance help + clarify = [cb(msg, details) + for cb in self.clarify_callbacks.get(mcu_msg, [])] + clarify = [cm for cm in clarify if cm is not None] + clarify_msg = "" + if clarify: + clarify_msg = "\n".join(["", ""] + clarify + [""]) # Update error message - newmsg = "%s%s%s%s" % (prefix, mcu_msg, hint, message_shutdown) + newmsg = "%s%s%s%s%s" % (prefix, mcu_msg, clarify_msg, + hint, message_shutdown) self.printer.update_error_msg(msg, newmsg) def _handle_notify_mcu_shutdown(self, msg, details): if msg == "MCU shutdown": From d89722056bf58103dc7fc06bc310ac39afa6aaa0 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 18 Jun 2024 13:01:34 -0400 Subject: [PATCH 186/190] mcu: Rename setup_minmax() to setup_adc_sample() Rename this method so that it is more distinct from the the common temperature setup_minmax() method. Signed-off-by: Kevin O'Connor --- klippy/extras/adc_scaled.py | 6 ++--- klippy/extras/adc_temperature.py | 8 +++---- klippy/extras/buttons.py | 2 +- klippy/extras/hall_filament_width_sensor.py | 4 ++-- klippy/extras/temperature_mcu.py | 23 ++++++++++--------- .../extras/tsl1401cl_filament_width_sensor.py | 2 +- klippy/mcu.py | 4 ++-- 7 files changed, 24 insertions(+), 25 deletions(-) diff --git a/klippy/extras/adc_scaled.py b/klippy/extras/adc_scaled.py index c2d2cb877f78..80ea452f3109 100644 --- a/klippy/extras/adc_scaled.py +++ b/klippy/extras/adc_scaled.py @@ -7,7 +7,6 @@ SAMPLE_TIME = 0.001 SAMPLE_COUNT = 8 REPORT_TIME = 0.300 -RANGE_CHECK_COUNT = 4 class MCU_scaled_adc: def __init__(self, main, pin_params): @@ -18,7 +17,7 @@ def __init__(self, main, pin_params): qname = main.name + ":" + pin_params['pin'] query_adc.register_adc(qname, self._mcu_adc) self._callback = None - self.setup_minmax = self._mcu_adc.setup_minmax + self.setup_adc_sample = self._mcu_adc.setup_adc_sample self.get_mcu = self._mcu_adc.get_mcu def _handle_callback(self, read_time, read_value): max_adc = self._main.last_vref[1] @@ -54,8 +53,7 @@ def _config_pin(self, config, name, callback): ppins = self.printer.lookup_object('pins') mcu_adc = ppins.setup_pin('adc', pin_name) mcu_adc.setup_adc_callback(REPORT_TIME, callback) - mcu_adc.setup_minmax(SAMPLE_TIME, SAMPLE_COUNT, minval=0., maxval=1., - range_check_count=RANGE_CHECK_COUNT) + mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT) query_adc = config.get_printer().load_object(config, 'query_adc') query_adc.register_adc(self.name + ":" + name, mcu_adc) return mcu_adc diff --git a/klippy/extras/adc_temperature.py b/klippy/extras/adc_temperature.py index b76e8c66fa85..260fe2817529 100644 --- a/klippy/extras/adc_temperature.py +++ b/klippy/extras/adc_temperature.py @@ -32,10 +32,10 @@ def adc_callback(self, read_time, read_value): temp = self.adc_convert.calc_temp(read_value) self.temperature_callback(read_time + SAMPLE_COUNT * SAMPLE_TIME, temp) def setup_minmax(self, min_temp, max_temp): - adc_range = [self.adc_convert.calc_adc(t) for t in [min_temp, max_temp]] - self.mcu_adc.setup_minmax(SAMPLE_TIME, SAMPLE_COUNT, - minval=min(adc_range), maxval=max(adc_range), - range_check_count=RANGE_CHECK_COUNT) + arange = [self.adc_convert.calc_adc(t) for t in [min_temp, max_temp]] + self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT, + minval=min(arange), maxval=max(arange), + range_check_count=RANGE_CHECK_COUNT) ###################################################################### diff --git a/klippy/extras/buttons.py b/klippy/extras/buttons.py index 70d76a60e175..daa998a93dcd 100644 --- a/klippy/extras/buttons.py +++ b/klippy/extras/buttons.py @@ -104,7 +104,7 @@ def __init__(self, printer, pin, pullup): self.max_value = 0. ppins = printer.lookup_object('pins') self.mcu_adc = ppins.setup_pin('adc', self.pin) - self.mcu_adc.setup_minmax(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT) + self.mcu_adc.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT) self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback) query_adc = printer.lookup_object('query_adc') query_adc.register_adc('adc_button:' + pin.strip(), self.mcu_adc) diff --git a/klippy/extras/hall_filament_width_sensor.py b/klippy/extras/hall_filament_width_sensor.py index e080288741d7..8dab35226666 100644 --- a/klippy/extras/hall_filament_width_sensor.py +++ b/klippy/extras/hall_filament_width_sensor.py @@ -49,10 +49,10 @@ def __init__(self, config): # Start adc self.ppins = self.printer.lookup_object('pins') self.mcu_adc = self.ppins.setup_pin('adc', self.pin1) - self.mcu_adc.setup_minmax(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT) + self.mcu_adc.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT) self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback) self.mcu_adc2 = self.ppins.setup_pin('adc', self.pin2) - self.mcu_adc2.setup_minmax(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT) + self.mcu_adc2.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT) self.mcu_adc2.setup_adc_callback(ADC_REPORT_TIME, self.adc2_callback) # extrude factor updating self.extrude_factor_update_timer = self.reactor.register_timer( diff --git a/klippy/extras/temperature_mcu.py b/klippy/extras/temperature_mcu.py index 585ec4c1d20d..02d91a3a459b 100644 --- a/klippy/extras/temperature_mcu.py +++ b/klippy/extras/temperature_mcu.py @@ -35,26 +35,27 @@ def __init__(self, config): query_adc.register_adc(config.get_name(), self.mcu_adc) # Register callbacks if self.printer.get_start_args().get('debugoutput') is not None: - self.mcu_adc.setup_minmax(SAMPLE_TIME, SAMPLE_COUNT, - range_check_count=RANGE_CHECK_COUNT) + self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT) return self.printer.register_event_handler("klippy:mcu_identify", - self._mcu_identify) + self.handle_mcu_identify) + # Temperature interface def setup_callback(self, temperature_callback): self.temperature_callback = temperature_callback def get_report_time_delta(self): return REPORT_TIME - def adc_callback(self, read_time, read_value): - temp = self.base_temperature + read_value * self.slope - self.temperature_callback(read_time + SAMPLE_COUNT * SAMPLE_TIME, temp) def setup_minmax(self, min_temp, max_temp): self.min_temp = min_temp self.max_temp = max_temp + # Internal code + def adc_callback(self, read_time, read_value): + temp = self.base_temperature + read_value * self.slope + self.temperature_callback(read_time + SAMPLE_COUNT * SAMPLE_TIME, temp) def calc_adc(self, temp): return (temp - self.base_temperature) / self.slope def calc_base(self, temp, adc): return temp - adc * self.slope - def _mcu_identify(self): + def handle_mcu_identify(self): # Obtain mcu information mcu = self.mcu_adc.get_mcu() self.debug_read_cmd = mcu.lookup_query_command( @@ -89,10 +90,10 @@ def _mcu_identify(self): self.slope = (self.temp2 - self.temp1) / (self.adc2 - self.adc1) self.base_temperature = self.calc_base(self.temp1, self.adc1) # Setup min/max checks - adc_range = [self.calc_adc(t) for t in [self.min_temp, self.max_temp]] - self.mcu_adc.setup_minmax(SAMPLE_TIME, SAMPLE_COUNT, - minval=min(adc_range), maxval=max(adc_range), - range_check_count=RANGE_CHECK_COUNT) + arange = [self.calc_adc(t) for t in [self.min_temp, self.max_temp]] + self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT, + minval=min(arange), maxval=max(arange), + range_check_count=RANGE_CHECK_COUNT) def config_unknown(self): raise self.printer.config_error("MCU temperature not supported on %s" % (self.mcu_type,)) diff --git a/klippy/extras/tsl1401cl_filament_width_sensor.py b/klippy/extras/tsl1401cl_filament_width_sensor.py index fb2d97131130..83480f46714a 100644 --- a/klippy/extras/tsl1401cl_filament_width_sensor.py +++ b/klippy/extras/tsl1401cl_filament_width_sensor.py @@ -33,7 +33,7 @@ def __init__(self, config): # Start adc self.ppins = self.printer.lookup_object('pins') self.mcu_adc = self.ppins.setup_pin('adc', self.pin) - self.mcu_adc.setup_minmax(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT) + self.mcu_adc.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT) self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback) # extrude factor updating self.extrude_factor_update_timer = self.reactor.register_timer( diff --git a/klippy/mcu.py b/klippy/mcu.py index feb4856a1d22..1122ff865845 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -496,8 +496,8 @@ def __init__(self, mcu, pin_params): self._inv_max_adc = 0. def get_mcu(self): return self._mcu - def setup_minmax(self, sample_time, sample_count, - minval=0., maxval=1., range_check_count=0): + def setup_adc_sample(self, sample_time, sample_count, + minval=0., maxval=1., range_check_count=0): self._sample_time = sample_time self._sample_count = sample_count self._min_sample = minval From 2d73211190e90f4dac0b8585fa931e8d46d4970e Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 18 Jun 2024 13:18:06 -0400 Subject: [PATCH 187/190] adc_temperature: Enhance "ADC out of range" error reports Try to report which ADC is reporting out of range. Signed-off-by: Kevin O'Connor --- klippy/extras/adc_temperature.py | 42 +++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/klippy/extras/adc_temperature.py b/klippy/extras/adc_temperature.py index 260fe2817529..c53ae7056adf 100644 --- a/klippy/extras/adc_temperature.py +++ b/klippy/extras/adc_temperature.py @@ -1,6 +1,6 @@ # Obtain temperature using linear interpolation of ADC values # -# Copyright (C) 2016-2018 Kevin O'Connor +# Copyright (C) 2016-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import logging, bisect @@ -22,8 +22,8 @@ def __init__(self, config, adc_convert): ppins = config.get_printer().lookup_object('pins') self.mcu_adc = ppins.setup_pin('adc', config.get('sensor_pin')) self.mcu_adc.setup_adc_callback(REPORT_TIME, self.adc_callback) - query_adc = config.get_printer().load_object(config, 'query_adc') - query_adc.register_adc(config.get_name(), self.mcu_adc) + self.diag_helper = HelperTemperatureDiagnostics( + config, self.mcu_adc, adc_convert.calc_temp) def setup_callback(self, temperature_callback): self.temperature_callback = temperature_callback def get_report_time_delta(self): @@ -33,9 +33,43 @@ def adc_callback(self, read_time, read_value): self.temperature_callback(read_time + SAMPLE_COUNT * SAMPLE_TIME, temp) def setup_minmax(self, min_temp, max_temp): arange = [self.adc_convert.calc_adc(t) for t in [min_temp, max_temp]] + min_adc, max_adc = sorted(arange) self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT, - minval=min(arange), maxval=max(arange), + minval=min_adc, maxval=max_adc, range_check_count=RANGE_CHECK_COUNT) + self.diag_helper.setup_diag_minmax(min_temp, max_temp, min_adc, max_adc) + +# Tool to register with query_adc and report extra info on ADC range errors +class HelperTemperatureDiagnostics: + def __init__(self, config, mcu_adc, calc_temp_cb): + self.printer = config.get_printer() + self.name = config.get_name() + self.mcu_adc = mcu_adc + self.calc_temp_cb = calc_temp_cb + self.min_temp = self.max_temp = self.min_adc = self.max_adc = None + query_adc = self.printer.load_object(config, 'query_adc') + query_adc.register_adc(self.name, self.mcu_adc) + error_mcu = self.printer.load_object(config, 'error_mcu') + error_mcu.add_clarify("ADC out of range", self._clarify_adc_range) + def setup_diag_minmax(self, min_temp, max_temp, min_adc, max_adc): + self.min_temp, self.max_temp = min_temp, max_temp + self.min_adc, self.max_adc = min_adc, max_adc + def _clarify_adc_range(self, msg, details): + if self.min_temp is None: + return None + last_value, last_read_time = self.mcu_adc.get_last_value() + if not last_read_time: + return None + if last_value >= self.min_adc and last_value <= self.max_adc: + return None + tempstr = "?" + try: + last_temp = self.calc_temp_cb(last_value) + tempstr = "%.3f" % (last_temp,) + except e: + logging.exception("Error in calc_temp callback") + return ("Sensor '%s' temperature %s not in range %.3f:%.3f" + % (self.name, tempstr, self.min_temp, self.max_temp)) ###################################################################### From 6d70050261ec3290f3c2e4015438e4910fd430d0 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 18 Jun 2024 13:22:48 -0400 Subject: [PATCH 188/190] temperature_mcu: Enhance "ADC out of range" error reports Try to report which ADC is reporting out of range. Signed-off-by: Kevin O'Connor --- klippy/extras/temperature_mcu.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/klippy/extras/temperature_mcu.py b/klippy/extras/temperature_mcu.py index 02d91a3a459b..be2cd145c455 100644 --- a/klippy/extras/temperature_mcu.py +++ b/klippy/extras/temperature_mcu.py @@ -1,10 +1,11 @@ # Support for micro-controller chip based temperature sensors # -# Copyright (C) 2020 Kevin O'Connor +# Copyright (C) 2020-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import logging import mcu +from . import adc_temperature SAMPLE_TIME = 0.001 SAMPLE_COUNT = 8 @@ -31,8 +32,8 @@ def __init__(self, config): self.mcu_adc = ppins.setup_pin('adc', '%s:ADC_TEMPERATURE' % (mcu_name,)) self.mcu_adc.setup_adc_callback(REPORT_TIME, self.adc_callback) - query_adc = config.get_printer().load_object(config, 'query_adc') - query_adc.register_adc(config.get_name(), self.mcu_adc) + self.diag_helper = adc_temperature.HelperTemperatureDiagnostics( + config, self.mcu_adc, self.calc_temp) # Register callbacks if self.printer.get_start_args().get('debugoutput') is not None: self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT) @@ -51,6 +52,8 @@ def setup_minmax(self, min_temp, max_temp): def adc_callback(self, read_time, read_value): temp = self.base_temperature + read_value * self.slope self.temperature_callback(read_time + SAMPLE_COUNT * SAMPLE_TIME, temp) + def calc_temp(self, adc): + return self.base_temperature + adc * self.slope def calc_adc(self, temp): return (temp - self.base_temperature) / self.slope def calc_base(self, temp, adc): @@ -91,9 +94,12 @@ def handle_mcu_identify(self): self.base_temperature = self.calc_base(self.temp1, self.adc1) # Setup min/max checks arange = [self.calc_adc(t) for t in [self.min_temp, self.max_temp]] + min_adc, max_adc = sorted(arange) self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT, - minval=min(arange), maxval=max(arange), + minval=min_adc, maxval=max_adc, range_check_count=RANGE_CHECK_COUNT) + self.diag_helper.setup_diag_minmax(self.min_temp, self.max_temp, + min_adc, max_adc) def config_unknown(self): raise self.printer.config_error("MCU temperature not supported on %s" % (self.mcu_type,)) From 4d21ffc1d67d4aa9886cc691441afccc057b975d Mon Sep 17 00:00:00 2001 From: elmo Date: Thu, 27 Jun 2024 18:59:48 +0200 Subject: [PATCH 189/190] config: Adds support for the Tronxy Crux1 printer (#6627) Signed-off-by: Louis West --- config/printer-tronxy-crux1-2022.cfg | 138 +++++++++++++++++++++++++++ test/klippy/printers.test | 1 + 2 files changed, 139 insertions(+) create mode 100644 config/printer-tronxy-crux1-2022.cfg diff --git a/config/printer-tronxy-crux1-2022.cfg b/config/printer-tronxy-crux1-2022.cfg new file mode 100644 index 000000000000..e3254d85b322 --- /dev/null +++ b/config/printer-tronxy-crux1-2022.cfg @@ -0,0 +1,138 @@ +# Klipper configuration for the TronXY Crux1 printer +# CXY-V10.1-220921 mainboard, GD32F4XX or STM32F446 MCU +# +# ======================= +# BUILD AND FLASH OPTIONS +# ======================= +# +# MCU-architecture: STMicroelectronics +# Processor model: STM32F446 +# Bootloader offset: 64KiB +# Comms interface: Serial on USART1 PA10/PA9 +# +# Build the firmware with these options +# Rename the resulting klipper.bin into fmw_tronxy.bin +# Put the file into a directory called "update" on a FAT32 formatted SD card. +# Turn off the printer, plug in the SD card and turn the printer back on +# Flashing will start automatically and progress will be indicated on the LCD +# Once the flashing is completed the display will get stuck on the white Tronxy logo bootscreen +# The LCD display will NOT work anymore after flashing Klipper onto this printer + +[mcu] +serial: /dev/serial/by-id/usb-1a86_USB_Serial-if00-port0 +restart_method: command + +[printer] +kinematics: cartesian +max_velocity: 250 +max_accel: 1500 +square_corner_velocity: 5 +max_z_velocity: 15 +max_z_accel: 100 + +[controller_fan drivers_fan] +pin: PD7 + +[pwm_cycle_time BEEPER_pin] +pin: PA8 +value: 0 +shutdown_value: 0 +cycle_time: 0.001 + +[safe_z_home] +home_xy_position: 0, 0 +speed: 100 +z_hop: 10 +z_hop_speed: 5 + +[stepper_x] +step_pin: PE5 +dir_pin: PF1 +enable_pin: !PF0 +microsteps: 16 +rotation_distance: 20 +endstop_pin: ^!PC15 +position_endstop: -1 +position_min: -1 +position_max: 180 +homing_speed: 100 +homing_retract_dist: 10 +second_homing_speed: 25 + +[stepper_y] +step_pin: PF9 +dir_pin: !PF3 +enable_pin: !PF5 +microsteps: 16 +rotation_distance: 20 +endstop_pin: ^!PC14 +position_endstop: -3 +position_min: -3 +position_max: 180 +homing_retract_dist: 10 +homing_speed: 100 +second_homing_speed: 25 + +[stepper_z] +step_pin: PA6 +dir_pin: !PF15 +enable_pin: !PA5 +microsteps: 16 +rotation_distance: 4 +endstop_pin: ^!PC13 +position_endstop: 0 +position_max: 180 +position_min: 0 + +[extruder] +step_pin: PB1 +dir_pin: PF13 +enable_pin: !PF14 +microsteps: 16 +rotation_distance: 16.75 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PG7 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC3 +control: pid +pid_kp: 22.2 +pid_ki: 1.08 +pid_kd: 114.00 +min_temp: 0 +max_temp: 250 +min_extrude_temp: 170 +max_extrude_only_distance: 450 + +[heater_fan hotend_fan] +heater: extruder +heater_temp: 50.0 +pin: PG9 + +[fan] +pin: PG0 + +[filament_switch_sensor filament_sensor] +pause_on_runout: True +switch_pin: ^!PE6 + +[heater_bed] +heater_pin: PE2 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC2 +min_temp: 0 +max_temp: 130 +control: pid +pid_kp: 10.00 +pid_ki: 0.023 +pid_kd: 305.4 + +[bed_screws] +screw1: 17.5, 11 +screw1_name: front_left +screw2: 162.5, 11 +screw2_name: front_right +screw3: 162.5, 162.5 +screw3_name: back_right +screw4: 17.5, 162.5 +screw4_name: back_left diff --git a/test/klippy/printers.test b/test/klippy/printers.test index ba7adb614f0c..9f7ab3c20f38 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -247,6 +247,7 @@ CONFIG ../../config/generic-fysetc-spider.cfg CONFIG ../../config/generic-ldo-leviathan-v1.2.cfg CONFIG ../../config/generic-mks-rumba32-v1.0.cfg CONFIG ../../config/printer-ratrig-v-minion-2021.cfg +CONFIG ../../config/printer-tronxy-crux1-2022.cfg # Printers using the stm32h723 DICTIONARY stm32h723.dict From 9318901f19de1cbee1cbf142f2f4bcccd86d053b Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 30 Jun 2024 15:39:46 -0400 Subject: [PATCH 190/190] mkdocs: Update id The Google UA ids are deprecated - update to assigned GA4 id. Signed-off-by: Kevin O'Connor --- docs/_klipper3d/mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_klipper3d/mkdocs.yml b/docs/_klipper3d/mkdocs.yml index c5da747b5c37..4bfdd169422a 100644 --- a/docs/_klipper3d/mkdocs.yml +++ b/docs/_klipper3d/mkdocs.yml @@ -71,7 +71,7 @@ extra: # https://squidfunk.github.io/mkdocs-material/setup/setting-up-site-analytics/#site-search-tracking analytics: provider: google - property: UA-138371409-1 + property: G-VEN1PGNQL4 # Language Selection alternate: - name: English