diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 06a6c6d99..1195734c1 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -2544,43 +2544,6 @@ 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. -#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. -#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 4ef4c0a2c..90cbce431 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -1197,28 +1197,6 @@ 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 diff --git a/klippy/extras/ldc1612.py b/klippy/extras/ldc1612.py deleted file mode 100644 index e0a936e39..000000000 --- a/klippy/extras/ldc1612.py +++ /dev/null @@ -1,252 +0,0 @@ -# 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 - -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 - - -# 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, 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 - 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 - self.ldc1612_setup_home_cmd = self.query_ldc1612_home_state_cmd = None - 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) - # Bulk sample message reading - chip_smooth = self.data_rate * BATCH_UPDATES * 2 - self.ffreader = bulk_sensor.FixedFreqReader(mcu, chip_smooth, ">I") - 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", "z") - 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.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" - " 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", - 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"]) - 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 - ) - - def add_client(self, cb): - self.batch_bulk.add_client(cb) - - # Homing - 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, hit_reason, err_reason] - ) - - def clear_home(self): - self.ldc1612_setup_home_cmd.send([self.oid, 0, 0, 0, 0, 0]) - if self.mcu.is_fileoutput(): - return 0.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 _convert_samples(self, samples): - freq_conv = float(LDC1612_FREQ) / (1 << 28) - 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 - - # 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.0 * (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.0 + 0.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, self.dccal.get_drive_current() << 11) - # Start bulk reading - 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.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.ffreader.note_end() - logging.info("LDC1612 finished '%s' measurements", self.name) - - def _process_batch(self, eventtime): - 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.ffreader.get_last_overflows(), - } diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py deleted file mode 100644 index 5d78bea0d..000000000 --- a/klippy/extras/probe_eddy_current.py +++ /dev/null @@ -1,390 +0,0 @@ -# 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 math, bisect -from klippy 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.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 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] - 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 - 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] - 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 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() - 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.0) - # 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 zpos in req_zpos: - # Move to next position (always descending to reduce backlash) - hop_pos = list(start_pos) - hop_pos[2] += zpos + 0.500 - move(hop_pos, move_speed) - next_pos = list(start_pos) - next_pos[2] += zpos - 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.0 - 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 - 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.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.0, above=0.0) - # Start manual probe - 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() - self._sensor_helper = sensor_helper - self._mcu = sensor_helper.get_mcu() - self._calibration = calibration - self._z_offset = config.getfloat("z_offset", minval=0.0) - self._dispatch = mcu.TriggerDispatch(self._mcu) - self._samples = [] - self._is_sampling = self._start_from_home = self._need_stop = False - self._trigger_time = 0.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 - 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 - - 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._trigger_time = 0.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( - print_time, - trigger_freq, - self._dispatch.get_oid(), - 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) - 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: - raise self._printer.command_error( - "Communication timeout during homing" - ) - if res != mcu.MCU_trsync.REASON_ENDSTOP_HIT: - return 0.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 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) - 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.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( - "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): - 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) - # 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) - - -def load_config_prefix(config): - return PrinterEddyProbe(config) diff --git a/scripts/motan/data_logger.py b/scripts/motan/data_logger.py index 6eb54a39a..6d296fd40 100755 --- a/scripts/motan/data_logger.py +++ b/scripts/motan/data_logger.py @@ -180,7 +180,6 @@ def handle_subscribe(self, msg, raw_msg): # 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 capprefix, st in sorted(stypes.items()): diff --git a/scripts/motan/readlog.py b/scripts/motan/readlog.py index aa8960b18..bbaa8cbbc 100644 --- a/scripts/motan/readlog.py +++ b/scripts/motan/readlog.py @@ -511,72 +511,6 @@ def interpolate(next_val, prev_val, next_time, prev_time, req_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.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.0 / next_freq - prev_val = 1.0 / 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.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 ###################################################################### diff --git a/src/Kconfig b/src/Kconfig index 21b0de2a0..0b3fbcfc8 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -114,10 +114,6 @@ config WANT_LIS2DW bool depends on HAVE_GPIO_SPI || HAVE_GPIO_I2C default y -config WANT_LDC1612 - bool - depends on HAVE_GPIO_I2C - default y config WANT_HX71X bool depends on WANT_GPIO_BITBANGING @@ -136,7 +132,7 @@ config WANT_SOFTWARE_SPI default y config NEED_SENSOR_BULK bool - depends on WANT_SENSORS || WANT_LIS2DW || WANT_LDC1612 || WANT_HX71X \ + depends on WANT_SENSORS || WANT_LIS2DW || WANT_HX71X \ || WANT_ADS1220 default y menu "Optional features (to reduce code size)" @@ -153,9 +149,6 @@ config WANT_SENSORS config WANT_LIS2DW bool "Support lis2dw and lis3dh 3-axis accelerometers" depends on HAVE_GPIO_SPI || HAVE_GPIO_I2C -config WANT_LDC1612 - bool "Support ldc1612 eddy current sensor" - depends on HAVE_GPIO_I2C config WANT_HX71X bool "Support HX711 and HX717 ADC chips" depends on WANT_GPIO_BITBANGING diff --git a/src/Makefile b/src/Makefile index 86c7407e6..073190e9c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -19,7 +19,6 @@ 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_WANT_HX71X) += sensor_hx71x.c src-$(CONFIG_WANT_ADS1220) += sensor_ads1220.c src-$(CONFIG_NEED_SENSOR_BULK) += sensor_bulk.c diff --git a/src/sensor_ldc1612.c b/src/sensor_ldc1612.c deleted file mode 100644 index 219583329..000000000 --- a/src/sensor_ldc1612.c +++ /dev/null @@ -1,251 +0,0 @@ -// 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/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, LDC_HAVE_INTB = 1<<1, - 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; - struct gpio_in intb_pin; - // homing - struct trsync *ts; - uint8_t homing_flags; - uint8_t trigger_reason, error_reason; - uint32_t trigger_threshold; - uint32_t homing_clock; -}; - -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) -{ - struct ldc1612 *ld = container_of(timer, struct ldc1612, timer); - if (ld->flags & LDC_PENDING) - ld->sb.possible_overflows++; - 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; -} - -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_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) -{ - 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->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 error_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"); - -// 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; - 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)) - 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 -#define REG_STATUS 0x18 - -// Read a register on the ldc1612 -static void -read_reg(struct ldc1612 *ld, uint8_t reg, uint8_t *res) -{ - int ret = i2c_dev_read(ld->i2c, sizeof(reg), ®, 2, res); - i2c_shutdown_on_err(ret); -} - -// 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) -{ - // Check if data available (and clear INTB line) - uint16_t status = read_reg_status(ld); - irq_disable(); - ld->flags &= ~LDC_PENDING; - irq_enable(); - if (!(status & 0x08)) - 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 - 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)) - 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 &= ~LDC_PENDING; - 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_status_ldc1612(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(); - - uint32_t fifo = status & 0x08 ? BYTES_PER_SAMPLE : 0; - sensor_bulk_status(&ld->sb, args[0], time1, time2-time1, fifo); -} -DECL_COMMAND(command_query_status_ldc1612, "query_status_ldc1612 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); diff --git a/test/configs/stm32f042.config b/test/configs/stm32f042.config index 53cf1281e..a16f7faea 100644 --- a/test/configs/stm32f042.config +++ b/test/configs/stm32f042.config @@ -3,6 +3,5 @@ CONFIG_MACH_STM32=y CONFIG_MACH_STM32F042=y CONFIG_WANT_SOFTWARE_I2C=n CONFIG_WANT_LIS2DW=n -CONFIG_WANT_LDC1612=n CONFIG_WANT_HX71X=n CONFIG_WANT_ADS1220=n