diff --git a/.github/workflows/stale-issue-bot.yaml b/.github/workflows/stale-issue-bot.yaml index b23511615555..9b03510928ae 100644 --- a/.github/workflows/stale-issue-bot.yaml +++ b/.github/workflows/stale-issue-bot.yaml @@ -334,7 +334,7 @@ jobs: if: github.repository == 'Klipper3d/klipper' runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v4 + - uses: dessant/lock-threads@v6 with: issue-inactive-days: '180' issue-lock-reason: '' diff --git a/docs/Load_Cell.md b/docs/Load_Cell.md index f3ff11e9221e..21987ec666c2 100644 --- a/docs/Load_Cell.md +++ b/docs/Load_Cell.md @@ -309,11 +309,12 @@ Set up z_thermal_adjust to reference the `extruder` as the source of temperature data. E.g.: ``` -[z_thermal_adjust nozzle] +[z_thermal_adjust] temp_coeff=-0.00045455 sensor_type: temperature_combined sensor_list: extruder combination_method: max +maximum_deviation: 999 min_temp: 0 max_temp: 400 max_z_adjustment: 0.1 diff --git a/docs/SDCard_Updates.md b/docs/SDCard_Updates.md index 432446de78cf..a1717ed26c73 100644 --- a/docs/SDCard_Updates.md +++ b/docs/SDCard_Updates.md @@ -48,7 +48,7 @@ All options can be viewed by the help screen: ./scripts/flash-sdcard.sh -h SD Card upload utility for Klipper -usage: flash_sdcard.sh [-h] [-l] [-c] [-b ] [-f ] +usage: flash_sdcard.sh [-h] [-l] [-c] [-s] [-b ] [-f ] positional arguments: @@ -59,6 +59,7 @@ optional arguments: -h show this message -l list available boards -c run flash check/verify only (skip upload) + -s use fast SPI speed (4MHz) -b serial baud rate (default is 250000) -f path to klipper.bin ``` @@ -87,6 +88,25 @@ use SDIO mode instead of SPI to access their SD Cards. (See Caveats below) But, it can also be used anytime to verify if the code flashed into the board matches the version in your build folder on any supported board. +## Failure to Initialize + +Some SD cards may fail to initialize at the default SPI speed of 400KHz. In +this situation it is possible to use `-s` to drive the SPI peripheral at 4MHz. +For example: + +``` +./scripts/flash-sdcard.sh -s /dev/ttyACM0 btt-skr-v1.3 +``` + +If the device still fails to initialize then cause of the failure is +unrelated to the speed and likely a result of one of the following +conditions: + +- The SD card is improperly formatted. Must be `fat` or `fat32`. +- Attempt to initialize a card using the SPI interface that has already + been initialized over SDIO. +- The SD card has failed or is corrupt. + ## Caveats - As mentioned in the introduction, this method only works for upgrading diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index 19e36ca42c47..32b20b8e0426 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -316,22 +316,29 @@ The following information is available for each `[led led_name]`, ## load_cell The following information is available for each `[load_cell name]`: -- 'is_calibrated': True/False is the load cell calibrated -- 'counts_per_gram': The number of raw sensor counts that equals 1 gram of force -- 'reference_tare_counts': The reference number of raw sensor counts for 0 force -- 'tare_counts': The current number of raw sensor counts for 0 force -- 'force_g': The force in grams, averaged over the last polling period. -- 'min_force_g': The minimum force in grams, over the last polling period. -- 'max_force_g': The maximum force in grams, over the last polling period. +- `is_calibrated`: True/False whether the load cell is calibrated. +- `counts_per_gram`: The number of raw sensor counts that equals 1 gram of force. +- `reference_tare_counts`: The reference number of raw sensor counts for 0 force. +- `tare_counts`: The current number of raw sensor counts for 0 force. +- `force_g`: The force in grams, averaged over the last polling period. +- `min_force_g`: The minimum force in grams, over the last polling period. +- `max_force_g`: The maximum force in grams, over the last polling period. +- `errors`: The number of sensor errors detected since the last start + of measurements. +- `overflows`: The number of data buffer overflows detected since the last + start of measurements. +- `sample_rate`: The sensor's sample rate in samples per second. ## load_cell_probe The following information is available for `[load_cell_probe]`: - all items from [load_cell](Status_Reference.md#load_cell) - all items from [probe](Status_Reference.md#probe) -- 'endstop_tare_counts': the load cell probe keeps a tare value independent of -the load cell. This re-set at the start of each probe. -- 'last_trigger_time': timestamp of the last homing trigger +- `endstop_tare_counts`: The load cell probe keeps a tare value independent of + the load cell. This is re-set at the start of each probe. +- `last_trigger_time`: Timestamp of the last homing trigger. +- `last_z_result`: The Z position result of the last tap. +- `is_last_tap_valid`: True if the last tap result is valid. ## manual_probe diff --git a/klippy/extras/ads1220.py b/klippy/extras/ads1220.py index f4ddaf9f8f63..af83da604c7c 100644 --- a/klippy/extras/ads1220.py +++ b/klippy/extras/ads1220.py @@ -120,6 +120,13 @@ def get_mcu(self): def get_samples_per_second(self): return self.sps + def get_status(self, eventtime): + return { + 'errors': self.last_error_count, + 'overflows': self.ffreader.get_last_overflows(), + 'sample_rate': self.get_samples_per_second(), + } + def lookup_sensor_error(self, error_code): return "Unknown ads1220 error" % (error_code,) diff --git a/klippy/extras/ads131m0x.py b/klippy/extras/ads131m0x.py index 9298edf70101..dccf6094c411 100644 --- a/klippy/extras/ads131m0x.py +++ b/klippy/extras/ads131m0x.py @@ -209,6 +209,13 @@ def get_mcu(self): def get_samples_per_second(self): return self.sps + def get_status(self, eventtime): + return { + 'errors': self.last_error_count, + 'overflows': self.ffreader.get_last_overflows(), + 'sample_rate': self.get_samples_per_second(), + } + def lookup_sensor_error(self, error_code): return self._sensor_errors.get(error_code, "Unknown %s error" % (self.sensor_type,)) diff --git a/klippy/extras/hx71x.py b/klippy/extras/hx71x.py index 76563d484494..90d5091a7b3e 100644 --- a/klippy/extras/hx71x.py +++ b/klippy/extras/hx71x.py @@ -78,6 +78,13 @@ def get_mcu(self): def get_samples_per_second(self): return self.sps + def get_status(self, eventtime): + return { + 'errors': self.last_error_count, + 'overflows': self.ffreader.get_last_overflows(), + 'sample_rate': self.get_samples_per_second(), + } + def lookup_sensor_error(self, error_code): return "Unknown hx71x error %d" % (error_code,) diff --git a/klippy/extras/load_cell.py b/klippy/extras/load_cell.py index 112d9640bdc9..5ba7467cc39e 100644 --- a/klippy/extras/load_cell.py +++ b/klippy/extras/load_cell.py @@ -286,12 +286,14 @@ def __init__(self, printer, load_cell): self._samples = [] self._errors = 0 self._overflows = 0 + self._start_errors = 0 + self._start_overflows = 0 def _on_samples(self, msg): if not self.is_started: return False # already stopped, ignore - self._errors += msg['errors'] - self._overflows += msg['overflows'] + self._errors = msg['errors'] + self._overflows = msg['overflows'] samples = msg['data'] for sample in samples: time = sample[0] @@ -310,9 +312,9 @@ def _finish_collecting(self): self.min_count = float("inf") # In Python 3.5 math.inf is better samples = self._samples self._samples = [] - errors = self._errors + errors = max(0, self._errors - self._start_errors) self._errors = 0 - overflows = self._overflows + overflows = max(0, self._overflows - self._start_overflows) self._overflows = 0 if self._mcu.is_fileoutput(): samples = [(0., 0., 0.)] @@ -323,10 +325,12 @@ def _collect_until(self, timeout): while self.is_started: now = self._reactor.monotonic() if self._mcu.estimated_print_time(now) > timeout: - _, (errors, overflows) = self._finish_collecting() + samples, err = self._finish_collecting() + errors, overflows = err if err else (0, 0) raise self._printer.command_error( - "LoadCellSampleCollector timed out! Errors: %i," - " Overflows: %i" % (errors, overflows)) + "LoadCellSampleCollector timed out! Collected %i samples," + " Errors: %i, Overflows: %i" % (len(samples), errors, + overflows)) if self._mcu.is_fileoutput(): break self._reactor.pause(now + RETRY_DELAY) @@ -338,6 +342,10 @@ def start_collecting(self, min_time=None): return self.min_time = min_time if min_time is not None else self.min_time self.is_started = True + sensor_status = self._load_cell.sensor.get_status( + self._reactor.monotonic()) + self._start_errors = sensor_status['errors'] + self._start_overflows = sensor_status['overflows'] self._load_cell.add_client(self._on_samples) # stop collecting immediately and return results @@ -517,6 +525,7 @@ def get_status(self, eventtime): 'counts_per_gram': self.counts_per_gram, 'reference_tare_counts': self.reference_tare_counts, 'tare_counts': self.tare_counts}) + status.update(self.sensor.get_status(eventtime)) return status diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index 8e877899c076..b5131f306bac 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -548,6 +548,160 @@ def probe_results_from_avg(measures, toolhead_pos, calibration, offsets): (offsets[0], offsets[1], sensor_z)) +###################################################################### +# Data fitting for "tap" +###################################################################### + +# Given a list of (frequency, z) pairs, find the coefficients +# z_contact, freq_contact, depress_slope, slope, and slope2 that best +# fit the data to the formulas `frequency = freq_contact + +# depress_slope*(z-z_contact)` when z<=z_contact and `frequency = +# freq_contact + slope*(z-z_contact) + slope2*(z-z_contact)*(z-z_contact)` +# when z>=z_contact. This implements a form of non-linear least +# squares. +class TapBestFit: + def __init__(self): + self._least_squares_cache = {} + def _build_ls_matrix(self, samples, est_z_contact): + # The function here is only a reference for the optimized version below + len_samples = len(samples) + eqs = [[0.] * 4 for i in range(len_samples)] + ans = [[0.] for i in range(len_samples)] + for i, (step_z, sensor_freq) in enumerate(samples): + ans[i][0] = sensor_freq + eq = eqs[i] + eq[0] = 1. + if step_z <= est_z_contact: + # 1*c0 + (z-ezc)*c1 + ezc*c2 + ezc*ezc*c3 = freq + eq[1] = step_z - est_z_contact + eq[2] = est_z_contact + eq[3] = est_z_contact * est_z_contact + else: + # 1*c0 + 0*c1 + z*c2 + z*z*c3 = freq + eq[1] = 0. + eq[2] = step_z + eq[3] = step_z * step_z + eqst = mathutil.mat_transp(eqs) + eqst_eqs = mathutil.mat_mat_mul(eqst, eqs) + eqst_ans = mathutil.mat_mat_mul(eqst, ans) + return eqst_eqs, eqst_ans + def _build_sums(self, samples, num_le): + sum_le_z = sum_le_z2 = sum_le_freq = sum_le_freq_z = 0. + for z, freq in samples[:num_le]: + sum_le_z += z + sum_le_z2 += z**2 + sum_le_freq += freq + sum_le_freq_z += freq*z + sum_gt_z = sum_gt_z2 = sum_gt_z3 = sum_gt_z4 = 0. + sum_gt_freq = sum_gt_freq_z = sum_gt_freq_z2 = 0. + for z, freq in samples[num_le:]: + sum_gt_z += z + sum_gt_z2 += z**2 + sum_gt_z3 += z**3 + sum_gt_z4 += z**4 + sum_gt_freq += freq + sum_gt_freq_z += freq*z + sum_gt_freq_z2 += freq * z**2 + return (sum_le_z, sum_le_z2, sum_le_freq, sum_le_freq_z, + sum_gt_z, sum_gt_z2, sum_gt_z3, sum_gt_z4, + sum_gt_freq, sum_gt_freq_z, sum_gt_freq_z2) + def _build_ls_matrix_opt(self, samples, est_z_contact): + # This function is an optimized version of _build_ls_matrix() + num_le = bisect.bisect(samples, (est_z_contact, sys.float_info.max)) + # Check for previously calculated raw freq/z counters + sums = self._least_squares_cache.get(num_le) + if sums is None: + sums = self._build_sums(samples, num_le) + self._least_squares_cache[num_le] = sums + (sum_le_z, sum_le_z2, sum_le_freq, sum_le_freq_z, + sum_gt_z, sum_gt_z2, sum_gt_z3, sum_gt_z4, + sum_gt_freq, sum_gt_freq_z, sum_gt_freq_z2) = sums + num_samples = len(samples) + ezc = est_z_contact + ezc2 = ezc**2 + ezc3 = ezc**3 + ezc4 = ezc**4 + # Build matrices for least squares evaluation + eqst_eqs = [[0.] * 4 for i in range(4)] + eqst_eqs[0][0] = num_samples + eqst_eqs[1][1] = sum_le_z2 - 2*ezc*sum_le_z + num_le*ezc2 + eqst_eqs[2][2] = sum_gt_z2 + num_le*ezc2 + eqst_eqs[3][3] = sum_gt_z4 + num_le*ezc4 + eqst_eqs[0][1] = eqst_eqs[1][0] = sum_le_z - num_le*ezc + eqst_eqs[0][2] = eqst_eqs[2][0] = sum_gt_z + num_le*ezc + eqst_eqs[0][3] = eqst_eqs[3][0] = sum_gt_z2 + num_le*ezc2 + eqst_eqs[2][3] = eqst_eqs[3][2] = sum_gt_z3 + num_le*ezc3 + eqst_eqs[2][1] = eqst_eqs[1][2] = ezc * eqst_eqs[0][1] + eqst_eqs[3][1] = eqst_eqs[1][3] = ezc2 * eqst_eqs[0][1] + eqst_ans = [[0.] for i in range(4)] + eqst_ans[0][0] = sum_le_freq + sum_gt_freq + eqst_ans[1][0] = sum_le_freq_z - ezc*sum_le_freq + eqst_ans[2][0] = sum_gt_freq_z + ezc*sum_le_freq + eqst_ans[3][0] = sum_gt_freq_z2 + ezc2 * sum_le_freq + return eqst_eqs, eqst_ans + def _calc_least_squares(self, samples, est_z_contact): + eqst_eqs, eqst_ans = self._build_ls_matrix_opt(samples, est_z_contact) + coeffs = mathutil.gaussian_solve(eqst_eqs, eqst_ans) + if coeffs is not None and coeffs[3][0] < 0.: + # z**2 factor can't be negative - retry using only linear + alt_eqst_eqs = [ee[:3] for ee in eqst_eqs[:3]] + alt_eqst_ans = eqst_ans[:3] + coeffs = mathutil.gaussian_solve(alt_eqst_eqs, alt_eqst_ans) + if coeffs is not None: + coeffs = coeffs + [[0.]] + if coeffs is None: + return sys.float_info.max, [[0.]] * 4 + rel_err = -sum([c[0]*a[0] for c, a in zip(coeffs, eqst_ans)]) + return rel_err, coeffs + def find_best_fit(self, data): + #for d in data: + # logging.info("sample: freq=%.3f z=%.6f", d[0], d[1][2]) + self._least_squares_cache.clear() + # Change base of freq/z measurements to improve numerical stability + base_z = .5 * (data[0][1][2] + data[-1][1][2]) + base_freq = .5 * (data[0][0] + data[-1][0]) + samples = [(d[1][2] - base_z, d[0] - base_freq) for d in data] + # Run least squares with various z values to reduce residual error + min_z = best_z = samples[0][0] + max_z = samples[-1][0] + best_err = sys.float_info.max + best_coeffs = [0., 0., 0., 0.] + while max_z - min_z > 0.000050: + # 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(samples, 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 + self._least_squares_cache.clear() + # Return to original freq/z measurement base + bc = [v[0] for v in best_coeffs] + z_contact = base_z + best_z + freq_contact = base_freq + bc[0] + best_z*bc[2] + best_z*best_z*bc[3] + depress_slope = bc[1] + slope = bc[2] + 2.*best_z*bc[3] + slope2 = bc[3] + #logging.info("probe_analysis: coeffs=%s", + # (z_contact, freq_contact, depress_slope, slope, slope2)) + return z_contact, freq_contact, depress_slope, slope, slope2 + + ###################################################################### # Probe sessions ###################################################################### @@ -623,7 +777,6 @@ def __init__(self, config, sensor_helper, param_helper, trigger_analog): self._filter_design = None self._tap_z_offset = config.getfloat('tap_z_offset', 0.) self._tap_threshold = config.getfloat('tap_threshold', 0., above=0.) - self._least_squares_cache = {} self._current_tap_threshold = 0. self._setup_tap() self._last_tap = None @@ -687,141 +840,6 @@ 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 _build_ls_matrix(self, samples, est_z_contact): - # The function here is only a reference for the optimized version below - len_samples = len(samples) - eqs = [[0.] * 4 for i in range(len_samples)] - ans = [[0.] for i in range(len_samples)] - for i, (step_z, sensor_freq) in enumerate(samples): - ans[i][0] = sensor_freq - eq = eqs[i] - eq[0] = 1. - if step_z <= est_z_contact: - # 1*c0 + (z-ezc)*c1 + ezc*c2 + ezc*ezc*c3 = freq - eq[1] = step_z - est_z_contact - eq[2] = est_z_contact - eq[3] = est_z_contact * est_z_contact - else: - # 1*c0 + 0*c1 + z*c2 + z*z*c3 = freq - eq[1] = 0. - eq[2] = step_z - eq[3] = step_z * step_z - eqst = mathutil.mat_transp(eqs) - eqst_eqs = mathutil.mat_mat_mul(eqst, eqs) - eqst_ans = mathutil.mat_mat_mul(eqst, ans) - return eqst_eqs, eqst_ans - def _build_sums(self, samples, num_le): - sum_le_z = sum_le_z2 = sum_le_freq = sum_le_freq_z = 0. - for z, freq in samples[:num_le]: - sum_le_z += z - sum_le_z2 += z**2 - sum_le_freq += freq - sum_le_freq_z += freq*z - sum_gt_z = sum_gt_z2 = sum_gt_z3 = sum_gt_z4 = 0. - sum_gt_freq = sum_gt_freq_z = sum_gt_freq_z2 = 0. - for z, freq in samples[num_le:]: - sum_gt_z += z - sum_gt_z2 += z**2 - sum_gt_z3 += z**3 - sum_gt_z4 += z**4 - sum_gt_freq += freq - sum_gt_freq_z += freq*z - sum_gt_freq_z2 += freq * z**2 - return (sum_le_z, sum_le_z2, sum_le_freq, sum_le_freq_z, - sum_gt_z, sum_gt_z2, sum_gt_z3, sum_gt_z4, - sum_gt_freq, sum_gt_freq_z, sum_gt_freq_z2) - def _build_ls_matrix_opt(self, samples, est_z_contact): - # This function is an optimized version of _build_ls_matrix() - num_le = bisect.bisect(samples, (est_z_contact, sys.float_info.max)) - # Check for previously calculated raw freq/z counters - sums = self._least_squares_cache.get(num_le) - if sums is None: - sums = self._build_sums(samples, num_le) - self._least_squares_cache[num_le] = sums - (sum_le_z, sum_le_z2, sum_le_freq, sum_le_freq_z, - sum_gt_z, sum_gt_z2, sum_gt_z3, sum_gt_z4, - sum_gt_freq, sum_gt_freq_z, sum_gt_freq_z2) = sums - num_samples = len(samples) - ezc = est_z_contact - ezc2 = ezc**2 - ezc3 = ezc**3 - ezc4 = ezc**4 - # Build matrices for least squares evaluation - eqst_eqs = [[0.] * 4 for i in range(4)] - eqst_eqs[0][0] = num_samples - eqst_eqs[1][1] = sum_le_z2 - 2*ezc*sum_le_z + num_le*ezc2 - eqst_eqs[2][2] = sum_gt_z2 + num_le*ezc2 - eqst_eqs[3][3] = sum_gt_z4 + num_le*ezc4 - eqst_eqs[0][1] = eqst_eqs[1][0] = sum_le_z - num_le*ezc - eqst_eqs[0][2] = eqst_eqs[2][0] = sum_gt_z + num_le*ezc - eqst_eqs[0][3] = eqst_eqs[3][0] = sum_gt_z2 + num_le*ezc2 - eqst_eqs[2][3] = eqst_eqs[3][2] = sum_gt_z3 + num_le*ezc3 - eqst_eqs[2][1] = eqst_eqs[1][2] = ezc * eqst_eqs[0][1] - eqst_eqs[3][1] = eqst_eqs[1][3] = ezc2 * eqst_eqs[0][1] - eqst_ans = [[0.] for i in range(4)] - eqst_ans[0][0] = sum_le_freq + sum_gt_freq - eqst_ans[1][0] = sum_le_freq_z - ezc*sum_le_freq - eqst_ans[2][0] = sum_gt_freq_z + ezc*sum_le_freq - eqst_ans[3][0] = sum_gt_freq_z2 + ezc2 * sum_le_freq - return eqst_eqs, eqst_ans - def _calc_least_squares(self, samples, est_z_contact): - eqst_eqs, eqst_ans = self._build_ls_matrix_opt(samples, est_z_contact) - coeffs = mathutil.gaussian_solve(eqst_eqs, eqst_ans) - if coeffs is not None and coeffs[3][0] < 0.: - # z**2 factor can't be negative - retry using only linear - alt_eqst_eqs = [ee[:3] for ee in eqst_eqs[:3]] - alt_eqst_ans = eqst_ans[:3] - coeffs = mathutil.gaussian_solve(alt_eqst_eqs, alt_eqst_ans) - if coeffs is not None: - coeffs = coeffs + [[0.]] - if coeffs is None: - return sys.float_info.max, [[0.]] * 4 - rel_err = -sum([c[0]*a[0] for c, a in zip(coeffs, eqst_ans)]) - return rel_err, coeffs - def _find_least_squares(self, data): - #for d in data: - # logging.info("sample: freq=%.3f z=%.6f", d[0], d[1][2]) - self._least_squares_cache.clear() - # Change base of freq/z measurements to improve numerical stability - base_z = .5 * (data[0][1][2] + data[-1][1][2]) - base_freq = .5 * (data[0][0] + data[-1][0]) - samples = [(d[1][2] - base_z, d[0] - base_freq) for d in data] - # Run least squares with various z values to reduce residual error - min_z = best_z = samples[0][0] - max_z = samples[-1][0] - best_err = sys.float_info.max - best_coeffs = [0., 0., 0., 0.] - while max_z - min_z > 0.000050: - # 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(samples, 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 - self._least_squares_cache.clear() - # Return to original freq/z measurement base - bc = [v[0] for v in best_coeffs] - final_coeffs = (base_z + best_z, - base_freq + bc[0] + best_z*bc[2] + best_z*best_z*bc[3], - bc[1], bc[2] + 2.*best_z*bc[3], bc[3]) - #logging.info("probe_analysis: coeffs=%s", final_coeffs) - return final_coeffs def _error_detect(self, msg): raise self._printer.command_error("Unable to detect tap: %s" % (msg,)) def _analyze_pullback(self, measures, start_time, end_time): @@ -838,7 +856,8 @@ def _analyze_pullback(self, measures, start_time, end_time): self._error_detect("insufficient lift (%.6f vs %.6f)" % (max_z - min_z, 0.350)) # Find best fit for extracted measurements - coeffs = self._find_least_squares(data) + tap_fit = TapBestFit() + coeffs = tap_fit.find_best_fit(data) z_contact, freq_contact, depress_slope, slope, slope2 = coeffs self._last_tap = ("fail", z_contact - min_z, coeffs) reactor.pause(0.) diff --git a/klippy/extras/sht3x.py b/klippy/extras/sht3x.py index 8151bd46242a..b5e1fd9fb62e 100644 --- a/klippy/extras/sht3x.py +++ b/klippy/extras/sht3x.py @@ -155,18 +155,11 @@ def _sample_sht3x(self, eventtime): self._callback(print_time, self.temp) return measured_time + self.report_time - def _split_bytes(self, data): - bytes = [] - for i in range((data.bit_length() + 7) // 8): - bytes.append((data >> i*8) & 0xFF) - bytes.reverse() - return bytes - def _crc8(self, data): #crc8 polynomial for 16bit value, CRC8 -> x^8 + x^5 + x^4 + 1 SHT3X_CRC8_POLYNOMINAL= 0x31 crc = 0xFF - data_bytes = self._split_bytes(data) + data_bytes = [data >> 8 & 0xFF, data & 0xFF] for byte in data_bytes: crc ^= byte for _ in range(8): diff --git a/scripts/flash-sdcard.sh b/scripts/flash-sdcard.sh index 6a08eab14eae..852540c27961 100755 --- a/scripts/flash-sdcard.sh +++ b/scripts/flash-sdcard.sh @@ -11,6 +11,7 @@ KLIPPER_DICT_DEFAULT="${SRCDIR}/out/klipper.dict" SPI_FLASH="${SRCDIR}/scripts/spi_flash/spi_flash.py" BAUD_ARG="" CHECK_ARG="" +SPI_SPEED_ARG="" # Force script to exit if an error occurs set -e @@ -18,7 +19,7 @@ print_help_message() { echo "SD Card upload utility for Klipper" echo - echo "usage: flash_sdcard.sh [-h] [-l] [-c] [-b ] [-f ] [-d ]" + echo "usage: flash_sdcard.sh [-h] [-l] [-c] [-s] [-b ] [-f ] [-d ]" echo " " echo echo "positional arguments:" @@ -29,13 +30,14 @@ print_help_message() echo " -h show this message" echo " -l list available boards" echo " -c run flash check/verify only (skip upload)" + echo " -s use fast SPI speed (4MHz)" echo " -b serial baud rate (default is 250000)" echo " -f path to klipper.bin" echo " -d path to klipper.dict for firmware validation" } # Parse command line "optional args" -while getopts "hlcb:f:d:" arg; do +while getopts "hlcsb:f:d:" arg; do case $arg in h) print_help_message @@ -46,6 +48,7 @@ while getopts "hlcb:f:d:" arg; do exit 0 ;; c) CHECK_ARG="-c";; + s) SPI_SPEED_ARG="-s";; b) BAUD_ARG="-b ${OPTARG}";; f) KLIPPER_BIN=$OPTARG;; d) KLIPPER_DICT=$OPTARG;; @@ -85,4 +88,4 @@ fi # Run Script echo "Flashing ${KLIPPER_BIN} to ${DEVICE}" -${KLIPPY_ENV} ${SPI_FLASH} ${CHECK_ARG} ${BAUD_ARG} ${KLIPPER_DICT} ${DEVICE} ${BOARD} ${KLIPPER_BIN} +${KLIPPY_ENV} ${SPI_FLASH} ${CHECK_ARG} ${SPI_SPEED_ARG} ${BAUD_ARG} ${KLIPPER_DICT} ${DEVICE} ${BOARD} ${KLIPPER_BIN} diff --git a/scripts/spi_flash/spi_flash.py b/scripts/spi_flash/spi_flash.py index af93b53e9637..16d5077b193b 100644 --- a/scripts/spi_flash/spi_flash.py +++ b/scripts/spi_flash/spi_flash.py @@ -17,7 +17,6 @@ import json import board_defs import fatfs_lib -import util import reactor import serialhdl import clocksync @@ -98,6 +97,7 @@ def check_need_convert(board_name, config): SDIO_OID = 0 SPI_MODE = 0 SD_SPI_SPEED = 400000 +FAST_SD_SPI_SPEED = 4000000 # MCU Command Constants RESET_CMD = "reset" GET_CFG_CMD = "get_config" @@ -1009,7 +1009,7 @@ def _check_command(self, check_func, cmd, args, is_app_cmd=False, tries=15, def _send_command(self, cmd, args, wait=0): cmd_code = SD_COMMANDS[cmd] argument = 0 - if isinstance(args, int) or isinstance(args, long): + if isinstance(args, int): argument = args & 0xFFFFFFFF elif isinstance(args, list) and len(args) == 4: argument = ((args[0] << 24) & 0xFF000000) | \ @@ -1303,6 +1303,9 @@ def check_need_restart(self): def _configure_mcu_spibus(self, printfunc=logging.info): # TODO: add commands for buttons? Or perhaps an endstop? We # just need to be able to query the status of the detect pin + fast_spi = self.board_config['fast_spi'] + spi_speed = FAST_SD_SPI_SPEED if fast_spi else SD_SPI_SPEED + output_line("Requested SPI Clock Frequency: %d" % (spi_speed,)) cs_pin = self.board_config['cs_pin'].upper() bus = self.board_config['spi_bus'] bus_enums = self.enumerations.get( @@ -1310,7 +1313,7 @@ def _configure_mcu_spibus(self, printfunc=logging.info): pin_enums = self.enumerations.get('pin') if bus == "swspi": mcu_freq = self.clocksync.print_time_to_clock(1) - pulse_ticks = mcu_freq//SD_SPI_SPEED + pulse_ticks = mcu_freq//spi_speed cfgpins = self.board_config['spi_pins'] pins = [p.strip().upper() for p in cfgpins.split(',') if p.strip()] pin_err_msg = "Invalid Software SPI Pins: %s" % (cfgpins,) @@ -1323,13 +1326,13 @@ def _configure_mcu_spibus(self, printfunc=logging.info): SW_SPI_BUS_CMDS[0] % (SPI_OID, pins[0], pins[1], pins[2], SPI_MODE, pulse_ticks), SW_SPI_BUS_CMDS[1] % (SPI_OID, pins[0], pins[1], pins[2], - SPI_MODE, SD_SPI_SPEED) + SPI_MODE, spi_speed) ] else: if bus not in bus_enums: raise SPIFlashError("Invalid SPI Bus: %s" % (bus,)) bus_cmds = [ - SPI_BUS_CMD % (SPI_OID, bus, SPI_MODE, SD_SPI_SPEED), + SPI_BUS_CMD % (SPI_OID, bus, SPI_MODE, spi_speed), ] if cs_pin not in pin_enums: raise SPIFlashError("Invalid CS Pin: %s" % (cs_pin,)) @@ -1682,6 +1685,10 @@ def main(): parser.add_argument( "-c","--check", action="store_true", help="Perform flash check/verify only") + parser.add_argument( + "-s", "--fast-spi", action="store_true", + help="Use fast SPI speed (4MHz)" + ) parser.add_argument( "device", metavar="", help="Device Serial Port") parser.add_argument( @@ -1701,6 +1708,7 @@ def main(): flash_args['klipper_bin_path'] = args.klipper_bin_path flash_args['klipper_dict_path'] = args.dict_path flash_args['verify_only'] = args.check + flash_args['fast_spi'] = args.fast_spi if args.check: # override board_defs setting when doing verify-only: flash_args['skip_verify'] = False diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index 9c7db5b5ee34..99059614e2e1 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -212,7 +212,8 @@ config CLOCK_FREQ default 170000000 if MACH_STM32G431 default 170000000 if MACH_STM32G474 default 520000000 if MACH_STM32H723 - default 400000000 if MACH_STM32H743 || MACH_STM32H750 + default 400000000 if MACH_STM32H743 + default 480000000 if MACH_STM32H750 default 80000000 if MACH_STM32L412 default 64000000 if MACH_N32G45x && STM32_CLOCK_REF_INTERNAL default 128000000 if MACH_N32G45x @@ -295,7 +296,7 @@ choice config STM32_FLASH_START_7000 bool "28KiB bootloader" if MACH_STM32F1 config STM32_FLASH_START_8000 - bool "32KiB bootloader" if MACH_STM32F1 || MACH_STM32F2 || MACH_STM32F4 || MACH_STM32F7 + bool "32KiB bootloader" if MACH_STM32F1 || MACH_STM32F2 || MACH_STM32F4 || MACH_STM32F7 || MACH_STM32H750 config STM32_FLASH_START_8800 bool "34KiB bootloader" if MACH_STM32F103 config STM32_FLASH_START_20200 @@ -314,7 +315,7 @@ choice config STM32_FLASH_START_4000 bool "16KiB bootloader" if MACH_STM32F207 || MACH_STM32F401 || MACH_STM32F4x5 || MACH_STM32F103 || MACH_STM32F072 config STM32_FLASH_START_20000 - bool "128KiB bootloader" if MACH_STM32H743 || MACH_STM32H723 || MACH_STM32F7 + bool "128KiB bootloader" if MACH_STM32H743 || MACH_STM32H723 || MACH_STM32F7 || MACH_STM32H750 config STM32_FLASH_START_0000 bool "No bootloader"