adxl345: Support recording data from multiple ADXL345's in one run, and more. (#5224)

Add PROBE and CHIP to TEST_RESONANCES

Since it's possible to specify more than one chip
in TEST_RESONANCES the CHIP parameter has been
renamed to CHIPS

Signed-off-by: Mikkel Schmidt <mikkel.schmidt@gmail.com>
This commit is contained in:
Mikkel Schmidt 2022-05-24 01:56:58 +02:00 committed by GitHub
parent c7e0372c5d
commit af38d708cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 72 additions and 24 deletions

View File

@ -896,23 +896,28 @@ all enabled accelerometer chips.
#### TEST_RESONANCES #### TEST_RESONANCES
`TEST_RESONANCES AXIS=<axis> OUTPUT=<resonances,raw_data> `TEST_RESONANCES AXIS=<axis> OUTPUT=<resonances,raw_data>
[NAME=<name>] [FREQ_START=<min_freq>] [FREQ_END=<max_freq>] [NAME=<name>] [FREQ_START=<min_freq>] [FREQ_END=<max_freq>]
[HZ_PER_SEC=<hz_per_sec>] [INPUT_SHAPING=[<0:1>]]`: Runs the resonance [HZ_PER_SEC=<hz_per_sec>] [CHIPS=<adxl345_chip_name>]
[POINT=x,y,z] [INPUT_SHAPING=[<0:1>]]`: Runs the resonance
test in all configured probe points for the requested "axis" and test in all configured probe points for the requested "axis" and
measures the acceleration using the accelerometer chips configured for measures the acceleration using the accelerometer chips configured for
the respective axis. "axis" can either be X or Y, or specify an 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 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 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` `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 and `AXIS=-dx,-dy` is equivalent. `adxl345_chip_name` can be one or
(default), disables input shaping for the resonance testing, because 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 it is not valid to run the resonance testing with the input shaper
enabled. `OUTPUT` parameter is a comma-separated list of which outputs enabled. `OUTPUT` parameter is a comma-separated list of which outputs
will be written. If `raw_data` is requested, then the raw will be written. If `raw_data` is requested, then the raw
accelerometer data is written into a file or a series of files accelerometer data is written into a file or a series of files
`/tmp/raw_data_<axis>_[<point>_]<name>.csv` with (`<point>_` part of `/tmp/raw_data_<axis>_[<chip_name>_][<point>_]<name>.csv` with
the name generated only if more than 1 probe point is configured). If (`<point>_` part of the name generated only if more than 1 probe point
`resonances` is specified, the frequency response is calculated is configured or POINT is specified). If `resonances` is specified, the
(across all probe points) and written into frequency response is calculated (across all probe points) and written into
`/tmp/resonances_<axis>_<name>.csv` file. If unset, OUTPUT defaults to `/tmp/resonances_<axis>_<name>.csv` file. If unset, OUTPUT defaults to
`resonances`, and NAME defaults to the current time in `resonances`, and NAME defaults to the current time in
"YYYYMMDD_HHMMSS" format. "YYYYMMDD_HHMMSS" format.

View File

@ -147,15 +147,21 @@ class ResonanceTester:
(chip_axis, self.printer.lookup_object(chip_name)) (chip_axis, self.printer.lookup_object(chip_name))
for chip_axis, chip_name in self.accel_chip_names] 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') toolhead = self.printer.lookup_object('toolhead')
calibration_data = {axis: None for axis in axes} calibration_data = {axis: None for axis in axes}
self.test.prepare_test(gcmd) self.test.prepare_test(gcmd)
if test_point is not None:
test_points = [test_point]
else:
test_points = self.test.get_start_test_points() test_points = self.test.get_start_test_points()
for point in test_points: for point in test_points:
toolhead.manual_move(point, self.move_speed) 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( gcmd.respond_info(
"Probing point (%.3f, %.3f, %.3f)" % tuple(point)) "Probing point (%.3f, %.3f, %.3f)" % tuple(point))
for axis in axes: for axis in axes:
@ -165,29 +171,36 @@ class ResonanceTester:
gcmd.respond_info("Testing axis %s" % axis.get_name()) gcmd.respond_info("Testing axis %s" % axis.get_name())
raw_values = [] raw_values = []
if accel_chips is None:
for chip_axis, chip in self.accel_chips: for chip_axis, chip in self.accel_chips:
if axis.matches(chip_axis): if axis.matches(chip_axis):
aclient = chip.start_internal_client() aclient = chip.start_internal_client()
raw_values.append((chip_axis, aclient)) raw_values.append((chip_axis, aclient, chip.name))
else:
for chip in accel_chips:
aclient = chip.start_internal_client()
raw_values.append((axis, aclient, chip.name))
# Generate moves # Generate moves
self.test.run_test(axis, gcmd) 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() aclient.finish_measurements()
if raw_name_suffix is not None: if raw_name_suffix is not None:
raw_name = self.get_filename( raw_name = self.get_filename(
'raw_data', raw_name_suffix, axis, '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) aclient.write_to_file(raw_name)
gcmd.respond_info( gcmd.respond_info(
"Writing raw accelerometer data to " "Writing raw accelerometer data to "
"%s file" % (raw_name,)) "%s file" % (raw_name,))
if helper is None: if helper is None:
continue continue
for chip_axis, aclient in raw_values: for chip_axis, aclient, chip_name in raw_values:
if not aclient.has_valid_samples(): if not aclient.has_valid_samples():
raise gcmd.error( raise gcmd.error(
"%s-axis accelerometer measured no data" % ( "accelerometer '%s' measured no data" % (
chip_axis,)) chip_name,))
new_data = helper.process_accelerometer_data(aclient) new_data = helper.process_accelerometer_data(aclient)
if calibration_data[axis] is None: if calibration_data[axis] is None:
calibration_data[axis] = new_data calibration_data[axis] = new_data
@ -198,6 +211,28 @@ class ResonanceTester:
def cmd_TEST_RESONANCES(self, gcmd): def cmd_TEST_RESONANCES(self, gcmd):
# Parse parameters # Parse parameters
axis = _parse_axis(gcmd, gcmd.get("AXIS").lower()) 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(',') outputs = gcmd.get("OUTPUT", "resonances").lower().split(',')
for output in outputs: for output in outputs:
@ -221,10 +256,13 @@ class ResonanceTester:
data = self._run_test( data = self._run_test(
gcmd, [axis], helper, 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: if csv_output:
csv_name = self.save_calibration_data('resonances', name_suffix, csv_name = self.save_calibration_data('resonances', name_suffix,
helper, axis, data) helper, axis, data,
point=test_point)
gcmd.respond_info( gcmd.respond_info(
"Resonances data written to %s file" % (csv_name,)) "Resonances data written to %s file" % (csv_name,))
cmd_SHAPER_CALIBRATE_help = ( cmd_SHAPER_CALIBRATE_help = (
@ -287,7 +325,8 @@ class ResonanceTester:
for chip_axis, aclient in raw_values: for chip_axis, aclient in raw_values:
if not aclient.has_valid_samples(): if not aclient.has_valid_samples():
raise gcmd.error( 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) data = helper.process_accelerometer_data(aclient)
vx = data.psd_x.mean() vx = data.psd_x.mean()
vy = data.psd_y.mean() vy = data.psd_y.mean()
@ -299,18 +338,22 @@ class ResonanceTester:
def is_valid_name_suffix(self, name_suffix): def is_valid_name_suffix(self, name_suffix):
return name_suffix.replace('-', '').replace('_', '').isalnum() 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 name = base
if axis: if axis:
name += '_' + axis.get_name() name += '_' + axis.get_name()
if chip_name:
name += '_' + chip_name.replace(" ", "_")
if point: if point:
name += "_%.3f_%.3f_%.3f" % (point[0], point[1], point[2]) name += "_%.3f_%.3f_%.3f" % (point[0], point[1], point[2])
name += '_' + name_suffix name += '_' + name_suffix
return os.path.join("/tmp", name + ".csv") return os.path.join("/tmp", name + ".csv")
def save_calibration_data(self, base_name, name_suffix, shaper_calibrate, def save_calibration_data(self, base_name, name_suffix, shaper_calibrate,
axis, calibration_data, all_shapers=None): axis, calibration_data,
output = self.get_filename(base_name, name_suffix, axis) all_shapers=None, point=None):
output = self.get_filename(base_name, name_suffix, axis, point)
shaper_calibrate.save_calibration_data(output, calibration_data, shaper_calibrate.save_calibration_data(output, calibration_data,
all_shapers) all_shapers)
return output return output