diff --git a/config/sample-cartographer-v3.cfg b/config/sample-cartographer-v3.cfg index ee1a6fd41794..a8b9508a0e4f 100644 --- a/config/sample-cartographer-v3.cfg +++ b/config/sample-cartographer-v3.cfg @@ -50,3 +50,4 @@ frequency: 24000000 i2c_address: 42 i2c_mcu: carto i2c_bus: i2c1_PB6_PB7 +descend_z: 0.5 diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index d50a0f8e6e51..7fe758d7a74d 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,19 @@ All dates in this document are approximate. ## Changes +20260318: The `[probe_eddy_current]` config options `speed`, +`lift_speed`, `samples`, `sample_retract_dist`, `samples_result`, +`samples_tolerance`, and `samples_tolerance_retries` no longer apply +to probe commands using `METHOD=scan`, `METHOD=rapid_scan`, nor +`METHOD=tap`. To use different settings, supply the equivalent +`PROBE_SPEED`, `LIFT_SPEED`, `SAMPLES`, `SAMPLE_RETRACT_DIST`, +`SAMPLES_RESULT`, `SAMPLES_TOLERANCE`, or `SAMPLES_TOLERANCE_RETRIES` +parameter with the probe command. + +20260318: The `[probe_eddy_current]` config option `z_offset` has been +renamed to `descend_z`. Using the old name is deprecated and it will +be removed in the near future. + 20260214: The `MANUAL_STEPPER` G-Code command `STOP_ON_ENDSTOP` parameter has changed. See the [MANUAL_STEPPER](G-Codes.md#manual_stepper) documentation for diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 50b1b30ac81f..d247a9eccc8c 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -2137,32 +2137,44 @@ z_offset: # The distance (in mm) between the bed and the nozzle when the probe # triggers. This parameter must be provided. #speed: 5.0 -# Speed (in mm/s) of the Z axis when probing. The default is 5mm/s. +# Speed (in mm/s) of the Z axis when probing. It may be possible to +# change this value at runtime via a "PROBE_SPEED" command +# parameter. The default is 5mm/s. #samples: 1 # The number of times to probe each point. The probed z-values will -# be averaged. The default is to probe 1 time. +# be averaged. It may be possible to change this value at runtime +# via a "SAMPLES" command parameter. The default is to probe 1 time. #sample_retract_dist: 2.0 # The distance (in mm) to lift the toolhead between each sample (if -# sampling more than once). The default is 2mm. +# sampling more than once). It may be possible to change this value +# at runtime via a "SAMPLE_RETRACT_DIST" command parameter. The +# default is 2mm. #lift_speed: # Speed (in mm/s) of the Z axis when lifting the probe between -# samples. The default is to use the same value as the 'speed' -# parameter. +# samples. It may be possible to change this value at runtime via a +# "LIFT_SPEED" command parameter. The default is to use the same +# value as the 'speed' parameter. #samples_result: average # The calculation method when sampling more than once - either -# "median" or "average". The default is average. +# "median" or "average". It may be possible to change this value at +# runtime via a "SAMPLES_RESULT" command parameter. The default is +# average. #samples_tolerance: 0.100 # The maximum Z distance (in mm) that a sample may differ from other # samples. If this tolerance is exceeded then either an error is # reported or the attempt is restarted (see -# samples_tolerance_retries). The default is 0.100mm. +# samples_tolerance_retries). It may be possible to change this +# value at runtime via a "SAMPLES_TOLERANCE" command parameter. The +# default is 0.100mm. #samples_tolerance_retries: 0 # The number of times to retry if a sample is found that exceeds # samples_tolerance. On a retry, all current samples are discarded # and the probe attempt is restarted. If a valid set of samples are # not obtained in the given number of retries then an error is -# reported. The default is zero which causes an error to be reported -# on the first sample that exceeds samples_tolerance. +# reported. It may be possible to change this value at runtime via a +# "SAMPLES_TOLERANCE_RETRIES" command parameter. The default is zero +# which causes an error to be reported on the first sample that +# exceeds samples_tolerance. #activate_gcode: # A list of G-Code commands to execute prior to each probe attempt. # See docs/Command_Templates.md for G-Code format. This may be @@ -2305,7 +2317,7 @@ sensor_type: 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: +#descend_z: # The nominal distance (in mm) between the nozzle and bed that a # probing attempt should stop at. This parameter must be provided. #i2c_address: @@ -2318,6 +2330,8 @@ sensor_type: ldc1612 # settings" section for a description of the above parameters. #x_offset: #y_offset: +# The distance (in mm) between the probe and the nozzle along the +# x and y axes. The default is 0. #speed: #lift_speed: #samples: @@ -2325,7 +2339,10 @@ sensor_type: ldc1612 #samples_result: #samples_tolerance: #samples_tolerance_retries: -# See the "probe" section for information on these parameters. +# See the "probe" section for information on these parameters. Note +# that the settings here apply only to regular probe commands. These +# settings do not have an effect if using a probe "METHOD" of +# "scan", "rapid_scan", or "tap". #tap_threshold: # Noise cutoff/stop trigger threshold (in Hz). Specify this value to # enable support for "METHOD=tap" probe commands. See Eddy_Probe.md @@ -2336,6 +2353,12 @@ sensor_type: ldc1612 # the bed. If this value is specified then one may override its # value at run-time using the "TAP_THRESHOLD" parameter on probe # commands. The default is to not enable support for "tap" probing. +#tap_z_offset: 0.0 +# The Z height (in mm) of the nozzle relative to the bed at the +# contact point detected during "tap" probing. Nominally this would +# be 0.0 to indicate the contact point has zero distance, but one +# may set this to account for backlash, thermal expansion, a +# systemic probing bias, or similar. The default is zero. ``` ### [axis_twist_compensation] diff --git a/docs/Eddy_Probe.md b/docs/Eddy_Probe.md index aaf50b7ffc00..14bc1e7e87c3 100644 --- a/docs/Eddy_Probe.md +++ b/docs/Eddy_Probe.md @@ -11,7 +11,7 @@ for further details. 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 +in the printer.cfg file. It is recommended to set `descend_z` 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. @@ -42,11 +42,11 @@ tool completes it will output the sensor performance data: ``` probe_eddy_current: noise 0.000642mm, MAD_Hz=11.314 in 2525 queries Total frequency range: 45000.012 Hz -z_offset: 0.250 # noise 0.000200mm, MAD_Hz=11.000 -z_offset: 0.530 # noise 0.000300mm, MAD_Hz=12.000 -z_offset: 1.010 # noise 0.000400mm, MAD_Hz=14.000 -z_offset: 2.010 # noise 0.000600mm, MAD_Hz=12.000 -z_offset: 3.010 # noise 0.000700mm, MAD_Hz=9.000 +z: 0.250 # noise 0.000200mm, MAD_Hz=11.000 +z: 0.530 # noise 0.000300mm, MAD_Hz=12.000 +z: 1.010 # noise 0.000400mm, MAD_Hz=14.000 +z: 2.010 # noise 0.000600mm, MAD_Hz=12.000 +z: 3.010 # noise 0.000700mm, MAD_Hz=9.000 ``` issue a `SAVE_CONFIG` command to save the results to the printer.cfg and restart. @@ -116,15 +116,6 @@ Practically, it ensures that the Eddy's output data absolute value change per second (velocity) is high enough - higher than the noise level, and that upon collision it always decreases by at least this value. -``` -[probe_eddy_current my_probe] -# eddy probe configuration... -# Recommended starting values for the tap -#samples: 3 -#samples_tolerance: 0.025 -#samples_tolerance_retries: 3 -``` - Before setting it to any other value, it is necessary to install `scipy`: ```bash @@ -167,7 +158,7 @@ calibration routine output: ``` probe_eddy_current: noise 0.000642mm, MAD_Hz=11.314 ... -z_offset: 1.010 # noise 0.000400mm, MAD_Hz=14.000 +z: 1.010 # noise 0.000400mm, MAD_Hz=14.000 ``` The estimation will be: ``` diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 9b876a059f67..1afaa2bb8c9e 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -1251,6 +1251,14 @@ additional parameters if a `[probe_eddy_current]` section is defined: specified in the `[probe_eddy_current]` config section when probing using `METHOD=tap`. +The `Z_OFFSET_APPLY_PROBE` command is also extended to support a +`METHOD=tap` parameter. When no METHOD parameter is provided, the +`Z_OFFSET_APPLY_PROBE` command alters the probe calibration to apply +the current Z G-Code offset to future `scan`, `rapid_scan`, and +default probes. If `METHOD=tap` is specified then the command instead +applies the change to `tap_z_offset` so that future `tap` probes are +updated to use the current Z G-Code offset. + #### PROBE_EDDY_CURRENT_CALIBRATE `PROBE_EDDY_CURRENT_CALIBRATE CHIP=`: This starts a tool that calibrates the sensor resonance frequencies to corresponding Z diff --git a/klippy/extras/load_cell_probe.py b/klippy/extras/load_cell_probe.py index 1e361d5d17ba..913b91a5b2ca 100644 --- a/klippy/extras/load_cell_probe.py +++ b/klippy/extras/load_cell_probe.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging, math import mcu -from . import probe, trigger_analog, load_cell, hx71x, ads1220 +from . import probe, manual_probe, trigger_analog, load_cell, hx71x, ads1220 np = None # delay NumPy import until configuration time @@ -426,7 +426,8 @@ def end_probe_session(self): # probe until a single good sample is returned or retries are exhausted def run_probe(self, gcmd): epos, is_good = self._tapping_move.run_tap(gcmd) - res = self._probe_offsets.create_probe_result(epos) + offsets = self._probe_offsets.get_offsets() + res = manual_probe.create_probe_result(epos, offsets) self._results.append(res) def pull_probed_results(self): diff --git a/klippy/extras/manual_probe.py b/klippy/extras/manual_probe.py index 727526916c07..f3d4e6a36405 100644 --- a/klippy/extras/manual_probe.py +++ b/klippy/extras/manual_probe.py @@ -1,6 +1,6 @@ # Helper script for manual z height probing # -# Copyright (C) 2019-2025 Kevin O'Connor +# Copyright (C) 2019-2026 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import logging, bisect, collections @@ -14,6 +14,13 @@ ProbeResult = collections.namedtuple('probe_result', [ 'bed_x', 'bed_y', 'bed_z', 'test_x', 'test_y', 'test_z']) +# Helper to create a ProbeResult from a test position and probe offsets +def create_probe_result(test_pos, offsets=(0., 0., 0.)): + x_offset, y_offset, z_offset = offsets + return ProbeResult( + test_pos[0]+x_offset, test_pos[1]+y_offset, test_pos[2]-z_offset, + test_pos[0], test_pos[1], test_pos[2]) + # Helper to lookup the Z stepper config section def lookup_z_endstop_config(config): if config.has_section('stepper_z'): @@ -283,7 +290,7 @@ def finalize(self, success): mpresult = None if success: kin_pos = self.get_kinematics_pos() - mpresult = ProbeResult(*(kin_pos[:3] + kin_pos[:3])) + mpresult = create_probe_result(kin_pos) self.finalize_callback(mpresult) def load_config(config): diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index 044875318e1c..28187bc52762 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -270,7 +270,8 @@ def run_probe(self, gcmd): speed = self.param_helper.get_probe_params(gcmd)['probe_speed'] phoming = self.printer.lookup_object('homing') ppos = phoming.probing_move(self.mcu_probe, pos, speed) - res = self.probe_offsets.create_probe_result(ppos) + offsets = self.probe_offsets.get_offsets() + res = manual_probe.create_probe_result(ppos, offsets) self.results.append(res) def pull_probed_results(self): res = self.results @@ -430,10 +431,6 @@ def __init__(self, config): self.z_offset = config.getfloat('z_offset') def get_offsets(self, gcmd=None): return self.x_offset, self.y_offset, self.z_offset - def create_probe_result(self, test_pos): - return manual_probe.ProbeResult( - test_pos[0]+self.x_offset, test_pos[1]+self.y_offset, - test_pos[2]-self.z_offset, test_pos[0], test_pos[1], test_pos[2]) ###################################################################### diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index a208c154e16d..94bf9a7f8771 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -1,9 +1,9 @@ # Support for eddy current based Z probes # -# Copyright (C) 2021-2024 Kevin O'Connor +# Copyright (C) 2021-2026 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import logging, math, bisect +import sys, logging, math, bisect import mcu from . import ldc1612, trigger_analog, probe, manual_probe @@ -206,7 +206,7 @@ def validate_calibration_data(self, positions): for pos, _, mad_hz, mad_mm in filtered: if len(points) and points[0] <= pos: points.pop(0) - msg = "z_offset: %.3f # noise %.6fmm, MAD_Hz=%.3f\n" % ( + msg = "z: %.3f # noise %.6fmm, MAD_Hz=%.3f\n" % ( pos, mad_mm, mad_hz) gcode.respond_info(msg) return filtered @@ -260,6 +260,19 @@ def _save_calibration(self, z_freq_pairs): cal_contents.pop() configfile = self.printer.lookup_object('configfile') configfile.set(self.name, 'calibrate', ''.join(cal_contents)) + def _save_tap_z_offset(self, gcmd, homing_z): + eventtime = self.printer.get_reactor().monotonic() + configfile = self.printer.lookup_object('configfile') + cstatus = configfile.get_status(eventtime) + csettings = cstatus.get('settings', {}).get(self.name, {}) + tap_z_offset = csettings.get('tap_z_offset', 0.) + new_calibrate = tap_z_offset - homing_z + gcmd.respond_info( + "%s: tap_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, 'tap_z_offset', "%.3f" % (new_calibrate,)) 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.) @@ -273,6 +286,9 @@ def cmd_Z_OFFSET_APPLY_PROBE(self, gcmd): if offset == 0: gcmd.respond_info("Nothing to do: Z Offset is 0") return + if gcmd.get("METHOD", "").lower() == "tap": + self._save_tap_z_offset(gcmd, offset) + return cal_zpos = [z - offset for z in self.cal_zpos] z_freq_pairs = zip(cal_zpos, self.cal_freqs) z_freq_pairs = sorted(z_freq_pairs) @@ -357,7 +373,7 @@ def _await_sensor_messages(self): "probe_eddy_current sensor outage") if mcu.is_fileoutput(): # In debugging mode - just create dummy response - dummy_pr = manual_probe.ProbeResult(0., 0., 0., 0., 0., 0.) + dummy_pr = manual_probe.create_probe_result((0., 0., 0.,)) self._analysis_results.append((dummy_pr, None)) self._probe_requests.pop(0) continue @@ -384,10 +400,8 @@ def probe_results_from_avg(measures, toolhead_pos, calibration, offsets): sensor_z = calibration.freq_to_height(freq_avg) if sensor_z <= -OUT_OF_RANGE or sensor_z >= OUT_OF_RANGE: raise cmderr("probe_eddy_current sensor not in valid range") - return manual_probe.ProbeResult( - toolhead_pos[0] + offsets[0], toolhead_pos[1] + offsets[1], - toolhead_pos[2] - sensor_z, - toolhead_pos[0], toolhead_pos[1], toolhead_pos[2]) + return manual_probe.create_probe_result(toolhead_pos, + (offsets[0], offsets[1], sensor_z)) MAX_VALID_RAW_VALUE=0x03ffffff @@ -401,6 +415,12 @@ def __init__(self, config, sensor_helper, calibration, self._probe_offsets = probe_offsets self._param_helper = param_helper self._trigger_analog = trigger_analog + if (config.get('z_offset', None, note_valid=False) is not None + and config.get('descend_z', None, note_valid=False) is None): + config.deprecate('z_offset') + self._descend_z = config.getfloat('z_offset', above=0.) + else: + self._descend_z = config.getfloat('descend_z', above=0.) self._z_min_position = probe.lookup_minimum_z(config) self._gather = None def _prep_trigger_analog(self): @@ -408,8 +428,7 @@ def _prep_trigger_analog(self): sos_filter.set_filter_design(None) sos_filter.set_offset_scale(0, 1.) self._trigger_analog.set_raw_range(0, MAX_VALID_RAW_VALUE) - z_offset = self._probe_offsets.get_offsets()[2] - trigger_freq = self._calibration.height_to_freq(z_offset) + trigger_freq = self._calibration.height_to_freq(self._descend_z) conv_freq = self._sensor_helper.convert_frequency(trigger_freq) self._trigger_analog.set_trigger('gt', conv_freq) # Probe session interface @@ -473,8 +492,7 @@ def probe_prepare(self, hmove): def probe_finish(self, hmove): pass def get_position_endstop(self): - z_offset = self._eddy_descend._probe_offsets.get_offsets()[2] - return z_offset + return self._eddy_descend._descend_z # Probing helper for "tap" requests class EddyTap: @@ -486,6 +504,7 @@ def __init__(self, config, sensor_helper, param_helper, trigger_analog): self._z_min_position = probe.lookup_minimum_z(config) self._gather = None self._filter_design = None + self._tap_z_offset = config.getfloat('tap_z_offset', 0.) self._tap_threshold = config.getfloat('tap_threshold', 0., above=0.) if self._tap_threshold: self._setup_tap() @@ -534,31 +553,6 @@ def _validate_samples_time(self, measures, start_time, end_time): if tmin < cycle_time * 0.75: raise cmderr("Eddy: CLKIN frequency too low: %.3f < %.3f" % (tmin, cycle_time * 0.75)) - def _central_diff(self, times, values): - velocity = [0.0] * len(values) - for i in range(1, len(values) - 1): - delta_v = (values[i+1] - values[i-1]) - delta_t = (times[i+1] - times[i-1]) - velocity[i] = delta_v / delta_t - velocity[0] = (values[1] - values[0]) / (times[1] - times[0]) - velocity[-1] = (values[-1] - values[-2]) / (times[-1] - times[-2]) - return velocity - def _pull_tap_time(self, measures): - tap_time = [] - tap_value = [] - for time, freq, z in measures: - tap_time.append(time) - tap_value.append(freq) - # Do the same filtering as on the MCU but without induced lag - main_design = self._filter_design.get_main_filter() - try: - fvals = main_design.filtfilt(tap_value) - except ValueError as e: - raise self._printer.command_error(str(e)) - velocity = self._central_diff(tap_time, fvals) - peak_velocity = max(velocity) - i = velocity.index(peak_velocity) - return tap_time[i] def _lookup_toolhead_pos(self, pos_time): toolhead = self._printer.lookup_object('toolhead') kin = toolhead.get_kinematics() @@ -566,11 +560,88 @@ def _lookup_toolhead_pos(self, pos_time): s.get_past_mcu_position(pos_time)) for s in kin.get_steppers()} return kin.calc_position(kin_spos) - def _analyze_tap(self, measures, start_time, end_time): + def _calc_least_squares(self, eqs, ans, est_z_contact): + # XXX - this implementation is not efficient + len_data = len(eqs) + import numpy + for i in range(len_data): + eq = eqs[i] + step_z = eq[1] + if step_z < est_z_contact: + eq[2] = eq[3] = 0. + continue + eq[2] = (step_z - est_z_contact) + eq[3] = (step_z - est_z_contact)**2 + res = numpy.linalg.lstsq(eqs, ans, rcond=None) + coeffs = list(res[0]) + if coeffs[3] < 0.: + # z**2 factor can't be negative - retry using only linear + res = numpy.linalg.lstsq(eqs[:][:,:3], ans, rcond=None) + coeffs = list(res[0]) + [0.] + if not res[1]: + err = sys.float_info.max + else: + err = res[1][0] + #logging.info("z=%.6f err=%.3f coeffs=%s", est_z_contact, err, coeffs) + return err, coeffs + def _find_least_squares(self, data): + len_data = len(data) + import numpy + # Populate initial numpy linear least squares arrays + eqs = numpy.zeros((len_data, 4)) + ans = numpy.zeros((len_data,)) + for i, (sensor_freq, tool_pos) in enumerate(data): + ans[i] = sensor_freq + eq = eqs[i] + eq[0] = 1. + eq[1] = tool_pos[2] + #logging.info("sample: freq=%.3f z=%.6f", sensor_freq, tool_pos[2]) + # Run least squares with various z values to reduce residual error + min_z = best_z = eqs[0][1] + max_z = eqs[-1][1] + best_err = sys.float_info.max + best_coeffs = [0., 0., 0., 0.] + while max_z - min_z > 0.000250: + # Select z value to check + mid_z = (min_z + max_z) * .5 + if best_z < mid_z: + guess_z = (best_z + max_z) * .5 + else: + guess_z = (min_z + best_z) * .5 + # Calculate least squares error for given z + guess_err, coeffs = self._calc_least_squares(eqs, ans, guess_z) + # Update search bounds + if guess_err < best_err: + if guess_z > best_z: + min_z = best_z + else: + max_z = best_z + best_z = guess_z + best_err = guess_err + best_coeffs = coeffs + else: + if guess_z > best_z: + max_z = guess_z + else: + min_z = guess_z + best_coeffs = [float(v) for v in best_coeffs] + #logging.info("best: z=%.6f err=%.6f coeffs=%s", + # best_z, best_err, best_coeffs) + return float(best_z), best_coeffs + def _analyze_pullback(self, measures, start_time, end_time): self._validate_samples_time(measures, start_time, end_time) - pos_time = self._pull_tap_time(measures) - trig_pos = self._lookup_toolhead_pos(pos_time) - return manual_probe.ProbeResult(trig_pos[0], trig_pos[1], trig_pos[2], + # Correlate measurements to toolhead position at time of measurement + data = [(sensor_freq, self._lookup_toolhead_pos(samp_time)) + for samp_time, sensor_freq, sensor_z in measures] + # Find best fit for extracted measurements + z_contact, coeffs = self._find_least_squares(data) + # Report probe position + trig_idx = len(data)-1 + while trig_idx > 0 and data[trig_idx-1][1][2] > z_contact: + trig_idx -= 1 + trig_pos = data[trig_idx][1] + adj_z_contact = z_contact - self._tap_z_offset + return manual_probe.ProbeResult(trig_pos[0], trig_pos[1], adj_z_contact, trig_pos[0], trig_pos[1], trig_pos[2]) # Probe session interface def start_probe_session(self, gcmd): @@ -581,20 +652,23 @@ def run_probe(self, gcmd): toolhead = self._printer.lookup_object('toolhead') pos = toolhead.get_position() pos[2] = self._z_min_position - speed = self._param_helper.get_probe_params(gcmd)['probe_speed'] - move_start_time = toolhead.get_last_move_time() + params = self._param_helper.get_probe_params(gcmd) + speed = params['probe_speed'] + lift_speed = params['lift_speed'] + lift_dist = gcmd.get_float('SAMPLE_RETRACT_DIST', 4., above=0.) # Perform probing move phoming = self._printer.lookup_object('homing') trig_pos = phoming.probing_move(self._trigger_analog, pos, speed) - # Extract samples - trigger_time = self._trigger_analog.get_last_trigger_time() - start_time = trigger_time - 0.250 - if start_time < move_start_time: - # Filter short move - start_time = move_start_time - end_time = trigger_time - self._gather.add_probe_request(self._analyze_tap, start_time, end_time, - start_time, end_time) + # Perform lifting move + haltpos = toolhead.get_position() + haltpos[2] += lift_dist + retract_start_time = toolhead.get_last_move_time() + toolhead.manual_move(haltpos, lift_speed) + # Extract retract samples + start_time = retract_start_time - 0.010 + end_time = retract_start_time + 0.150 + self._gather.add_probe_request(self._analyze_pullback, start_time, + end_time, start_time, end_time) def pull_probed_results(self): return self._gather.pull_probed() def end_probe_session(self): @@ -638,7 +712,7 @@ def start_probe_session(self, gcmd): self._calibration.verify_calibrated() self._gather = EddyGatherSamples(self._printer, self._sensor_helper) self._sample_time = gcmd.get_float("SAMPLE_TIME", 0.100, above=0.0) - self._is_rapid = gcmd.get("METHOD", "scan") == 'rapid_scan' + self._is_rapid = gcmd.get("METHOD", "scan").lower() == 'rapid_scan' return self def run_probe(self, gcmd): toolhead = self._printer.lookup_object("toolhead") @@ -664,6 +738,39 @@ def end_probe_session(self): self._gather.finish() self._gather = None +# Eddy specific ProbeOffsets class (does not store z_offset) +class EddyProbeOffsets: + def __init__(self, config): + self.x_offset = config.getfloat('x_offset', 0.) + self.y_offset = config.getfloat('y_offset', 0.) + def get_offsets(self, gcmd=None): + return self.x_offset, self.y_offset, 0. + +# Wrapper around ProbeParameterHelper +class EddyParameterHelper: + def __init__(self, config): + self._param_helper = probe.ProbeParameterHelper(config) + def get_probe_params(self, gcmd=None): + method = None + if gcmd is not None: + method = gcmd.get('METHOD', '').lower() + if method not in ['scan', 'rapid_scan', 'tap']: + return self._param_helper.get_probe_params(gcmd) + probe_speed = gcmd.get_float("PROBE_SPEED", 5.0, above=0.) + lift_speed = gcmd.get_float("LIFT_SPEED", 5.0, above=0.) + samples = gcmd.get_int("SAMPLES", 1, minval=1) + samp_retract_dist = 0. + samp_tolerance = gcmd.get_float("SAMPLES_TOLERANCE", 0.100, minval=0.) + samp_retries = gcmd.get_int("SAMPLES_TOLERANCE_RETRIES", 0, minval=0) + samples_result = gcmd.get("SAMPLES_RESULT", 'average') + return {'probe_speed': probe_speed, + 'lift_speed': lift_speed, + 'samples': samples, + 'sample_retract_dist': samp_retract_dist, + 'samples_tolerance': samp_tolerance, + 'samples_tolerance_retries': samp_retries, + 'samples_result': samples_result} + # Main "printer object" class PrinterEddyProbe: def __init__(self, config): @@ -677,8 +784,8 @@ def __init__(self, config): trig_analog = trigger_analog.MCU_trigger_analog(self.sensor_helper) probe.LookupZSteppers(config, trig_analog.get_dispatch().add_stepper) # Basic probe requests - self.probe_offsets = probe.ProbeOffsetsHelper(config) - self.param_helper = probe.ProbeParameterHelper(config) + self.probe_offsets = EddyProbeOffsets(config) + self.param_helper = EddyParameterHelper(config) self.eddy_descend = EddyDescend( config, self.sensor_helper, self.calibration, self.probe_offsets, self.param_helper, trig_analog) diff --git a/src/avr/adc.c b/src/avr/adc.c index 99fd063f6963..e94dfbc57006 100644 --- a/src/avr/adc.c +++ b/src/avr/adc.c @@ -4,6 +4,7 @@ // // This file may be distributed under the terms of the GNU GPLv3 license. +#include // ADCSRA #include "autoconf.h" // CONFIG_MACH_atmega644p #include "command.h" // shutdown #include "gpio.h" // gpio_adc_read diff --git a/src/avr/main.c b/src/avr/main.c index 0523af411156..b0d299dac879 100644 --- a/src/avr/main.c +++ b/src/avr/main.c @@ -12,6 +12,12 @@ #include "irq.h" // irq_enable #include "sched.h" // sched_main +// Newer avr-libc headers expose the stack pointer as SP instead of the +// older AVR_STACK_POINTER_REG alias used by Klipper. +#ifndef AVR_STACK_POINTER_REG +#define AVR_STACK_POINTER_REG SP +#endif + DECL_CONSTANT_STR("MCU", CONFIG_MCU); diff --git a/src/avr/spi.c b/src/avr/spi.c index 55eb1f58c991..d8acd45daf18 100644 --- a/src/avr/spi.c +++ b/src/avr/spi.c @@ -4,6 +4,7 @@ // // This file may be distributed under the terms of the GNU GPLv3 license. +#include // SPCR #include "autoconf.h" // CONFIG_MACH_atmega644p #include "command.h" // shutdown #include "gpio.h" // spi_setup diff --git a/src/rp2040/Kconfig b/src/rp2040/Kconfig index 481761b3d49b..97c31b859c06 100644 --- a/src/rp2040/Kconfig +++ b/src/rp2040/Kconfig @@ -14,8 +14,6 @@ config RPXXXX_SELECT select HAVE_GPIO_HARD_PWM select HAVE_STEPPER_OPTIMIZED_BOTH_EDGE select HAVE_BOOTLOADER_REQUEST - # Software divide needed on rp2040 for rate calculation in i2c.c - select HAVE_SOFTWARE_DIVIDE_REQUIRED if MACH_RP2040 config BOARD_DIRECTORY string diff --git a/src/rp2040/i2c.c b/src/rp2040/i2c.c index 8ec2bd7a6684..0bec61c29eef 100644 --- a/src/rp2040/i2c.c +++ b/src/rp2040/i2c.c @@ -101,10 +101,14 @@ i2c_setup(uint32_t bus, uint32_t rate, uint8_t addr) // See `i2c_set_baudrate` in the Pico SDK `hardware_i2c/i2c.c` file // for details on the calculations here. - if (rate > 1000000) - rate = 1000000; // Clamp the rate to 1Mbps - uint32_t period = (pclk + rate / 2) / rate; - uint32_t lcnt = period * 3 / 5; + uint32_t period; + if (rate >= 1000000) + period = DIV_ROUND_CLOSEST(pclk, 1000000); + else if (rate >= 400000) + period = DIV_ROUND_CLOSEST(pclk, 400000); + else + period = DIV_ROUND_CLOSEST(pclk, 100000); + uint32_t lcnt = DIV_ROUND_CLOSEST(period * ((3<<16) / 5), 1<<16); // 60% uint32_t hcnt = period - lcnt; uint32_t sda_tx_hold_count = ((pclk * 3) / 10000000) + 1; diff --git a/test/klippy/eddy.cfg b/test/klippy/eddy.cfg index 6f26899ef9e5..fd6a2a005ced 100644 --- a/test/klippy/eddy.cfg +++ b/test/klippy/eddy.cfg @@ -57,7 +57,7 @@ min_temp: 0 max_temp: 130 [probe_eddy_current eddy] -z_offset: 0.4 +descend_z: 0.4 x_offset: -5 y_offset: -4 sensor_type: ldc1612