From 49c144fbf8078deda845d1e2635ac7e83d7e0754 Mon Sep 17 00:00:00 2001 From: Jason Pell Date: Mon, 13 Apr 2026 12:01:18 +1000 Subject: [PATCH] latest eddy-ng python changes --- klippy/extras/ldc1612_ng.py | 34 +++++++++++++++++++---- klippy/extras/probe_eddy_ng.py | 51 ++++++++++++++++++++++++---------- src/sensor_ldc1612_ng.c | 4 +++ 3 files changed, 69 insertions(+), 20 deletions(-) diff --git a/klippy/extras/ldc1612_ng.py b/klippy/extras/ldc1612_ng.py index a618283ae7f4..69ac99bf8153 100644 --- a/klippy/extras/ldc1612_ng.py +++ b/klippy/extras/ldc1612_ng.py @@ -52,6 +52,7 @@ PRODUCT_BTT_EDDY = 1 PRODUCT_CARTOGRAPHER = 2 PRODUCT_MELLOW_FLY = 3 +PRODUCT_LDC1612_INTERNAL_CLK = 4 HOME_MODE_NONE = 0 HOME_MODE_HOME = 1 @@ -86,6 +87,7 @@ def __init__(self, config): "btt_eddy": PRODUCT_BTT_EDDY, "cartographer": PRODUCT_CARTOGRAPHER, "mellow_fly": PRODUCT_MELLOW_FLY, + "ldc1612_internal_clk": PRODUCT_LDC1612_INTERNAL_CLK, } self._device_product = config.getchoice("sensor_type", device_choices, PRODUCT_UNKNOWN) @@ -103,8 +105,15 @@ def __init__(self, config): self._ldc_fref_divider = 2 self._ldc_settle_time = 0.00125 self._default_drive_current = 15 + elif self._device_product == PRODUCT_LDC1612_INTERNAL_CLK: + # A generic setup that usees internal LDC1612 clock + # using LDC1612 internal typical clock frequency 43.4MHz + self._ldc_freq_clk = 43_400_000 + self._ldc_fin_divider = 1 + self._ldc_fref_divider = 1 + self._ldc_settle_time = 0.00125 + self._default_drive_current = 15 else: # Generic/BTT Eddy using external 12MHz clock source - # TODO add a generic setup that usees internal ldc1612 clock self._ldc_freq_clk = 12_000_000 self._ldc_settle_time = 0.005 self._ldc_fin_divider = 1 @@ -211,7 +220,10 @@ def handle_batch(msg): toolhead.dwell(0.100) toolhead.wait_moves() old_config = self.read_reg(REG_CONFIG) - self.set_reg(REG_CONFIG, 0x001 | (1 << 9)) + if (self._device_product == PRODUCT_LDC1612_INTERNAL_CLK): + self.set_reg(REG_CONFIG, 0x001) + else: + self.set_reg(REG_CONFIG, 0x001 | (1 << 9)) toolhead.wait_moves() toolhead.dwell(0.100) toolhead.wait_moves() @@ -260,8 +272,13 @@ def _build_config(self): cq=cmdqueue, ) - # XXX move this to a totally separate thing at some point - self._mcu.register_response(self._handle_debug_print, "debug_print") + if hasattr(self._mcu, "register_serial_response"): + # infuriating: these used to be able to be registered for optional + # things (that the firmware never sends) + #self._mcu.register_serial_response(self._handle_debug_print, "debug_print m=%*s") + pass + else: + self._mcu.register_response(self._handle_debug_print, "debug_print") def _handle_debug_print(self, params): logging.info(params["m"]) @@ -473,8 +490,13 @@ def _init_chip(self): ) self.set_reg(REG_ERROR_CONFIG, 0b1111_1100_1111_1001) # report everything to STATUS and INTB except ZC self.set_reg(REG_MUX_CONFIG, 0x0208 | deglitch) - # RP_OVERRIDE_EN | AUTO_AMP_DIS | REF_CLK_SRC=clkin | reserved - self.set_reg(REG_CONFIG, (1 << 12) | (1 << 10) | (1 << 9) | 0x001) + if (self._device_product == PRODUCT_LDC1612_INTERNAL_CLK): + # use internal oscillator + # RP_OVERRIDE_EN | AUTO_AMP_DIS | reserved + self.set_reg(REG_CONFIG, (1 << 12) | (1 << 10) | 0x001) + else: + # RP_OVERRIDE_EN | AUTO_AMP_DIS | REF_CLK_SRC=clkin | reserved + self.set_reg(REG_CONFIG, (1 << 12) | (1 << 10) | (1 << 9) | 0x001) self.set_reg(REG_DRIVE_CURRENT0, self._drive_current << 11) self._chip_initialized = True diff --git a/klippy/extras/probe_eddy_ng.py b/klippy/extras/probe_eddy_ng.py index ec40fe2ff174..fb3bf6266ab0 100644 --- a/klippy/extras/probe_eddy_ng.py +++ b/klippy/extras/probe_eddy_ng.py @@ -24,6 +24,7 @@ from dataclasses import dataclass, field from typing import ( + Any, Dict, List, Optional, @@ -43,6 +44,7 @@ from klippy.extras.homing import HomingMove IS_KALICO = True + HAS_PROBE_RESULT_TYPE = False except ImportError: import mcu import pins @@ -56,6 +58,7 @@ from .homing import HomingMove IS_KALICO = False + HAS_PROBE_RESULT_TYPE = hasattr(manual_probe, "ProbeResult") from . import ldc1612_ng @@ -228,6 +231,8 @@ class ProbeEddyParams: tap_max_samples: int = 5 # The maximum standard deviation for any 3 samples to be considered valid. tap_samples_stddev: float = 0.020 + # Use the median value instead of the mean + tap_use_median: bool = False # Where in the time range of tap detection start to the time the threshold # is crossed should the tap be placed. 0.0 places it at the earliest start # of tap detection; 1.0 places it at the point where the threshold is hit. @@ -323,6 +328,7 @@ def load_from_config(self, config: ConfigWrapper): self.tap_samples = config.getint("tap_samples", self.tap_samples, minval=1) self.tap_max_samples = config.getint("tap_max_samples", self.tap_max_samples, minval=self.tap_samples) self.tap_samples_stddev = config.getfloat("tap_samples_stddev", self.tap_samples_stddev, above=0.0) + self.tap_use_median = config.getboolean("tap_use_median", self.tap_use_median) self.tap_trigger_safe_start_height = config.getfloat( "tap_trigger_safe_start_height", -1.0, @@ -437,6 +443,7 @@ def __init__(self, config: ConfigWrapper): "btt_eddy": ldc1612_ng.LDC1612_ng, "cartographer": ldc1612_ng.LDC1612_ng, "mellow_fly": ldc1612_ng.LDC1612_ng, + "ldc1612_internal_clk": ldc1612_ng.LDC1612_ng, } sensor_type = config.getchoice("sensor_type", {s: s for s in sensors}) @@ -1116,7 +1123,7 @@ def cmd_SETUP_next(self, gcmd: GCodeCommand, kin_pos: Optional[List[float]]): ) max_dc_increase = 0 - if self._sensor_type == "ldc1612" or self._sensor_type == "btt_eddy": + if self._sensor_type == "ldc1612" or self._sensor_type == "btt_eddy" or self._sensor_type == "ldc1612_internal_clk": max_dc_increase = 5 max_dc_increase = gcmd.get_int("MAX_DC_INCREASE", max_dc_increase, minval=0, maxval=30) @@ -1418,7 +1425,7 @@ def cmd_TEST_DRIVE_CURRENT(self, gcmd: GCodeCommand): # PrinterProbe interface # - def get_offsets(self): + def get_offsets(self, *args, **kwargs): # the z offset is the trigger height, because the probe will trigger # at z=trigger_height (not at z=0) return ( @@ -1478,7 +1485,7 @@ def multi_probe_end(self): # This is a mishmash of cmd_PROBE and cmd_PROBE_STATIC. This run_probe # is the old one, different than the scanning session run_probe. - def run_probe(self, gcmd=None): + def run_probe(self, gcmd=None, *args: Any, **kwargs: Any): z = self.params.home_trigger_height duration = 0.100 @@ -1796,6 +1803,7 @@ def cmd_TAP_next(self, gcmd: Optional[GCodeCommand] = None): samples = gcmd.get_int("SAMPLES", self.params.tap_samples, minval=1) max_samples = gcmd.get_int("MAX_SAMPLES", self.params.tap_max_samples, minval=samples) samples_stddev = gcmd.get_float("SAMPLES_STDDEV", self.params.tap_samples_stddev, above=0.0) + use_median: bool = gcmd.get_int("USE_MEDIAN", 1 if self.params.tap_use_median else 0) == 1 home_z: bool = gcmd.get_int("HOME_Z", 1) == 1 write_plot_arg: int = gcmd.get_int("PLOT", None) @@ -1901,7 +1909,7 @@ def cmd_TAP_next(self, gcmd: Optional[GCodeCommand] = None): break if len(results) >= samples: - tap_z, tap_stddev, tap_overshoot = self._compute_tap_z(results, samples, samples_stddev) + tap_z, tap_stddev, tap_overshoot = self._compute_tap_z(results, samples, samples_stddev, use_median) if tap_z is not None: break finally: @@ -1994,7 +2002,7 @@ def cmd_TAP_next(self, gcmd: Optional[GCodeCommand] = None): # Compute the average tap_z from a set of tap results, taking a cluster of samples # from the result that has the lowest standard deviation - def _compute_tap_z(self, taps: List[ProbeEddy.TapResult], samples: int, req_stddev: float) -> Tuple[float, float, float]: + def _compute_tap_z(self, taps: List[ProbeEddy.TapResult], samples: int, req_stddev: float, use_median: bool) -> Tuple[float, float, float]: if len(taps) < samples: return None, None, None @@ -2004,12 +2012,19 @@ def _compute_tap_z(self, taps: List[ProbeEddy.TapResult], samples: int, req_stdd for cluster in combinations(taps, samples): tap_zs = np.array([t.probe_z for t in cluster]) overshoots = np.array([t.overshoot for t in cluster]) - mean = np.mean(tap_zs) std = np.std(tap_zs) if std < std_min: std_min = std - tap_z = mean - overshoot = np.mean(overshoots) + if use_median: + # we need the corresponding overshoot as well, so + # can't just use np.median(). + sorted_indices = np.argsort(tap_zs) + idx = len(tap_zs) // 2 + tap_z = tap_zs[sorted_indices[idx]] + overshoot = overshoots[sorted_indices[idx]] + else: + tap_z = np.mean(tap_zs) + overshoot = np.mean(overshoots) if std_min <= req_stddev: return float(tap_z), float(std_min), float(overshoot) @@ -2223,7 +2238,7 @@ def _rapid_lookahead_cb(self, time, th_pos): start_time = time - self._sample_time / 2.0 self._notes.append([start_time, time, th_pos]) - def run_probe(self, gcmd): + def run_probe(self, gcmd, *args: Any, **kwargs: Any): th = self._toolhead th_pos = th.get_position() @@ -2281,15 +2296,23 @@ def pull_probed_results(self): # the probe would 'trigger'", because this is all done in terms of klicky-type probes z = float(self._scan_z + z_deviation) - results.append([th_pos[0], th_pos[1], z]) + if HAS_PROBE_RESULT_TYPE: + bed_x = th_pos[0] + self.eddy.params.x_offset + bed_y = th_pos[1] + self.eddy.params.y_offset + res = manual_probe.ProbeResult(bed_x, bed_y, z_deviation, + th_pos[0], th_pos[1], th_pos[2]) + result_wrapper = [res] + self._printer.send_event("probe:update_results", result_wrapper) + res = result_wrapper[0] + else: + res = [th_pos[0], th_pos[1], z] + self._printer.send_event("probe:update_results", res) + + results.append(res) # reset notes so that this session can continue to be used self._notes = [] - # Allow axis_twist_compensation to update results - for epos in results: - self._printer.send_event("probe:update_results", epos) - return results diff --git a/src/sensor_ldc1612_ng.c b/src/sensor_ldc1612_ng.c index f6de6dac3460..310b8d67b895 100644 --- a/src/sensor_ldc1612_ng.c +++ b/src/sensor_ldc1612_ng.c @@ -65,6 +65,7 @@ enum { #define PRODUCT_BTT_EDDY 1 #define PRODUCT_CARTOGRAPHER 2 #define PRODUCT_MELLOW_FLY 3 +#define PRODUCT_LDC1612_INTERNAL_CLK 4 // Chip registers #define REG_DATA0_MSB 0x00 @@ -348,6 +349,9 @@ config_ldc1612_ng(uint32_t oid, uint32_t i2c_oid, uint8_t product, int32_t intb_ // pull that out on the python side. break; #endif + case PRODUCT_LDC1612_INTERNAL_CLK: + ld->sensor_cvt = 43400000.0f / (float)(1<<28); + break; default: shutdown("ldc1612_ng: unknown product"); }