diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 4dcbe729..eedb84cc 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -896,23 +896,28 @@ all enabled accelerometer chips. #### TEST_RESONANCES `TEST_RESONANCES AXIS= OUTPUT= [NAME=] [FREQ_START=] [FREQ_END=] -[HZ_PER_SEC=] [INPUT_SHAPING=[<0:1>]]`: Runs the resonance +[HZ_PER_SEC=] [CHIPS=] +[POINT=x,y,z] [INPUT_SHAPING=[<0:1>]]`: Runs the resonance test in all configured probe points for the requested "axis" and measures the acceleration using the accelerometer chips configured for the respective axis. "axis" can either be X or Y, or specify an arbitrary direction as `AXIS=dx,dy`, where dx and dy are floating point numbers defining a direction vector (e.g. `AXIS=X`, `AXIS=Y`, or `AXIS=1,-1` to define a diagonal direction). Note that `AXIS=dx,dy` -and `AXIS=-dx,-dy` is equivalent. If `INPUT_SHAPING=0` or not set -(default), disables input shaping for the resonance testing, because +and `AXIS=-dx,-dy` is equivalent. `adxl345_chip_name` can be one or +more configured adxl345 chip,delimited with comma, for example +`CHIPS="adxl345, adxl345 rpi"`. Note that `adxl345` can be omitted from +named adxl345 chips. If POINT is specified it will override the point(s) +configured in `[resonance_tester]`. If `INPUT_SHAPING=0` or not set(default), +disables input shaping for the resonance testing, because it is not valid to run the resonance testing with the input shaper enabled. `OUTPUT` parameter is a comma-separated list of which outputs will be written. If `raw_data` is requested, then the raw accelerometer data is written into a file or a series of files -`/tmp/raw_data__[_].csv` with (`_` part of -the name generated only if more than 1 probe point is configured). If -`resonances` is specified, the frequency response is calculated -(across all probe points) and written into +`/tmp/raw_data__[_][_].csv` with +(`_` part of the name generated only if more than 1 probe point +is configured or POINT is specified). If `resonances` is specified, the +frequency response is calculated (across all probe points) and written into `/tmp/resonances__.csv` file. If unset, OUTPUT defaults to `resonances`, and NAME defaults to the current time in "YYYYMMDD_HHMMSS" format. diff --git a/klippy/extras/resonance_tester.py b/klippy/extras/resonance_tester.py index 00797156..db03e320 100644 --- a/klippy/extras/resonance_tester.py +++ b/klippy/extras/resonance_tester.py @@ -147,15 +147,21 @@ class ResonanceTester: (chip_axis, self.printer.lookup_object(chip_name)) for chip_axis, chip_name in self.accel_chip_names] - def _run_test(self, gcmd, axes, helper, raw_name_suffix=None): + def _run_test(self, gcmd, axes, helper, raw_name_suffix=None, + accel_chips=None, test_point=None): toolhead = self.printer.lookup_object('toolhead') calibration_data = {axis: None for axis in axes} self.test.prepare_test(gcmd) - test_points = self.test.get_start_test_points() + + if test_point is not None: + test_points = [test_point] + else: + test_points = self.test.get_start_test_points() + for point in test_points: toolhead.manual_move(point, self.move_speed) - if len(test_points) > 1: + if len(test_points) > 1 or test_point is not None: gcmd.respond_info( "Probing point (%.3f, %.3f, %.3f)" % tuple(point)) for axis in axes: @@ -165,29 +171,36 @@ class ResonanceTester: gcmd.respond_info("Testing axis %s" % axis.get_name()) raw_values = [] - for chip_axis, chip in self.accel_chips: - if axis.matches(chip_axis): + if accel_chips is None: + for chip_axis, chip in self.accel_chips: + if axis.matches(chip_axis): + aclient = chip.start_internal_client() + raw_values.append((chip_axis, aclient, chip.name)) + else: + for chip in accel_chips: aclient = chip.start_internal_client() - raw_values.append((chip_axis, aclient)) + raw_values.append((axis, aclient, chip.name)) + # Generate moves self.test.run_test(axis, gcmd) - for chip_axis, aclient in raw_values: + for chip_axis, aclient, chip_name in raw_values: aclient.finish_measurements() if raw_name_suffix is not None: raw_name = self.get_filename( 'raw_data', raw_name_suffix, axis, - point if len(test_points) > 1 else None) + point if len(test_points) > 1 else None, + chip_name if accel_chips is not None else None,) aclient.write_to_file(raw_name) gcmd.respond_info( "Writing raw accelerometer data to " "%s file" % (raw_name,)) if helper is None: continue - for chip_axis, aclient in raw_values: + for chip_axis, aclient, chip_name in raw_values: if not aclient.has_valid_samples(): raise gcmd.error( - "%s-axis accelerometer measured no data" % ( - chip_axis,)) + "accelerometer '%s' measured no data" % ( + chip_name,)) new_data = helper.process_accelerometer_data(aclient) if calibration_data[axis] is None: calibration_data[axis] = new_data @@ -198,6 +211,28 @@ class ResonanceTester: def cmd_TEST_RESONANCES(self, gcmd): # Parse parameters axis = _parse_axis(gcmd, gcmd.get("AXIS").lower()) + accel_chips = gcmd.get("CHIPS", None) + test_point = gcmd.get("POINT", None) + + if test_point: + test_coords = test_point.split(',') + if len(test_coords) != 3: + raise gcmd.error("Invalid POINT parameter, must be 'x,y,z'") + try: + test_point = [float(p.strip()) for p in test_coords] + except ValueError: + raise gcmd.error("Invalid POINT parameter, must be 'x,y,z'" + " where x, y and z are valid floating point numbers") + + if accel_chips: + parsed_chips = [] + for chip_name in accel_chips.split(','): + if "adxl345" in chip_name: + chip_lookup_name = chip_name.strip() + else: + chip_lookup_name = "adxl345 " + chip_name.strip(); + chip = self.printer.lookup_object(chip_lookup_name) + parsed_chips.append(chip) outputs = gcmd.get("OUTPUT", "resonances").lower().split(',') for output in outputs: @@ -221,10 +256,13 @@ class ResonanceTester: data = self._run_test( gcmd, [axis], helper, - raw_name_suffix=name_suffix if raw_output else None)[axis] + raw_name_suffix=name_suffix if raw_output else None, + accel_chips=parsed_chips if accel_chips else None, + test_point=test_point)[axis] if csv_output: csv_name = self.save_calibration_data('resonances', name_suffix, - helper, axis, data) + helper, axis, data, + point=test_point) gcmd.respond_info( "Resonances data written to %s file" % (csv_name,)) cmd_SHAPER_CALIBRATE_help = ( @@ -287,7 +325,8 @@ class ResonanceTester: for chip_axis, aclient in raw_values: if not aclient.has_valid_samples(): raise gcmd.error( - "%s-axis accelerometer measured no data" % (chip_axis,)) + "%s-axis accelerometer measured no data" % ( + chip_axis,)) data = helper.process_accelerometer_data(aclient) vx = data.psd_x.mean() vy = data.psd_y.mean() @@ -299,18 +338,22 @@ class ResonanceTester: def is_valid_name_suffix(self, name_suffix): return name_suffix.replace('-', '').replace('_', '').isalnum() - def get_filename(self, base, name_suffix, axis=None, point=None): + def get_filename(self, base, name_suffix, axis=None, + point=None, chip_name=None): name = base if axis: name += '_' + axis.get_name() + if chip_name: + name += '_' + chip_name.replace(" ", "_") if point: name += "_%.3f_%.3f_%.3f" % (point[0], point[1], point[2]) name += '_' + name_suffix return os.path.join("/tmp", name + ".csv") def save_calibration_data(self, base_name, name_suffix, shaper_calibrate, - axis, calibration_data, all_shapers=None): - output = self.get_filename(base_name, name_suffix, axis) + axis, calibration_data, + all_shapers=None, point=None): + output = self.get_filename(base_name, name_suffix, axis, point) shaper_calibrate.save_calibration_data(output, calibration_data, all_shapers) return output