hx711s: read-on-DRDY acquisition loop with in-driver recovery - #6
Closed
pdscomp wants to merge 3 commits into
Closed
hx711s: read-on-DRDY acquisition loop with in-driver recovery#6pdscomp wants to merge 3 commits into
pdscomp wants to merge 3 commits into
Conversation
Make the MCU driver self-healing so recoverable acquisition faults never reach the host as fatal errors: - Torn reads (a chip latching a new conversion mid-read, ~0.4% of frames from the ~50us read window vs the 11.6ms conversion period) are no longer consumed or discarded. The driver emits nothing and re-reads on the next DRDY, bounded at 2 retries before degrading to the host's benign hold-last-value path. Every reference implementation waits for DRDY and re-reads rather than consume a raced frame: Prusa HX717 (src/common/hx717.cpp), Linux IIO (drivers/iio/adc/hx711.c), upstream Klipper (src/sensor_hx71x.c), and Elegoo's stock firmware (bounded ready-poll plus protected transfer, per the 1.6.9/1.7.0 images). - A gain-pulse desync means the serial protocol state no longer matches the chip, which only a power cycle clears (HX711F datasheet, power-down control: SCK high >60us). The driver now power-cycles in-driver and discards the 4 settling conversions the datasheet requires at 80 SPS before resuming the stream (~115ms pause). Previously the host force-restarted the whole sensor, which killed any active probe/tare session. Elegoo's stock firmware performs the same power-cycle recovery. - A chip that stops signaling DRDY for ~2.5 conversion periods (its oscillator free-runs, so silence is never legitimate) takes the same power-cycle path instead of freezing the stream. - The host is notified once per recovery via a RECOVERED marker frame carrying the torn-retry and recovery tallies, so reliability stays observable in the klippy log without escalating to a restart. The per-channel spike filter is unchanged: those glitches pass protocol framing, so no read-timing fix can catch them.
Move-start acceleration kicks (500 mm/s^2 Z moves) ring the bed frame and produce single-sample force impulses of ~200-250g on the channel sum that fully revert on the next sample (measured from recorded tap curves on the CC1 test printer: the same ~220g one-sample blip appears at probing-move start and at pullback-move start). They pass the per-channel glitch filter (100k-count threshold) because they are too small, and they pass the tare because they are brief. When one lands in the arming window of a probing move it fires a first-crossing trigger up to 1.2mm above the real contact point: the resulting tap window contains no contact ramp and the tap validator aborts with TAP_CHRONOLOGY. Real contact at 2mm/s rises by at most ~80g/sample sustained (also from the recorded taps), so any single-sample jump over ~190g on the sum is now held for one sample and only forwarded to the trigger if the next sample confirms it as a genuine step (the same hold-confirm pattern as the per-channel glitch filter). Real contact ramps never engage the hold, so trigger latency is unchanged; genuine steps are delayed by at most one sample. The raw per-channel stream still carries the impulse to the host for diagnostics.
The previous 190g threshold missed the small end of the impulse class: a captured raw force stream (182k samples, full mesh macro runs on the CC1 test printer) shows the killer event was an isolated +87g single-sample blip 0.5s after travel vibration settled -- 87g is under the 190g hold threshold but over the 75g trigger force, so it fired 'probe triggered prior to movement' in the arming window. The impulse class spans at least 87-220g, all single-sample and instantly reverting, so the hold threshold now sits just under trigger_force (67g): any blip big enough to trigger is held, and only released if the next sample confirms it. The confirm check is now sign-aware: an up-jump confirms when the next sample stays above (pending - threshold), which steep real contact ramps (+80g/sample sustained) satisfy, while phantoms revert and are dropped. The old symmetric |current - pending| <= threshold check would never confirm a monotonic ramp (each sample is more than threshold above the previous), needlessly stalling the trigger feed. Validated by replaying the full 182k-sample capture through a model of the filter: all 72 phantom crossings (1-sample blips, 60-220g) are removed; all 433 real contact ramps still trigger; the fatal +87g blip at the failure timestamp is held and dropped.
Author
|
Closing because OBE (rebasing fixes on james-new branch!) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Built on james's
hx711s-newbranch (multi-sensor driver, corrupt-frame filter, tap recorder) — this PR rewrites its acquisition loop around read-on-DRDY with in-driver recovery, replacing poll-and-classify with a design where recoverable faults never reach the host.Validated on CC1 hardware (4× HX711, 80 SPS): 30/30 consecutive full bed-mesh runs with zero faults — 10× at 60 °C bed, 20× at 100 °C (chamber ~60 °C). Supersedes #5 (same base branch; validated old stack remains at
c368f84e).The driver polls each chip's DRDY, verifies every frame, and re-reads on the next DRDY instead of consuming or discarding raced frames — the same acquisition contract used by Prusa's HX717, the Linux IIO hx711 driver, and upstream Klipper's hx71x. Protocol corruption is cleared by an in-driver power cycle with the datasheet-mandated settling conversions discarded, instead of a host-forced sensor restart that kills any active probe session.
Files changed
Only 2 files, both owned by the hx711s driver. No changes to shared probing, retry, or load-cell framework logic.
src/sensor_hx711s.cklippy/extras/load_cell/hx711s.pyEverything else in the driver (per-channel corrupt-frame filter, lockstep read, tap recorder) is james's
hx711s-new, unchanged.Failure taxonomy fixed (all captured on hardware)
The impulse class was the campaign-killer, in two captured manifestations:
TAP_CHRONOLOGYabort.Probe triggered prior to movement.Real contact at 2 mm/s rises ≤ ~80 g/sample sustained; impulses exceed trigger force for exactly one sample. The driver holds any single-sample jump > 67 g (just under the 75 g trigger) from the trigger feed and releases it only if the next sample confirms it sustained (sign-aware: an up-jump confirms if the level holds, so steep real ramps pass with ≤1 sample latency). The raw stream is untouched — impulses still reach the host for diagnostics.
Why MCU-side sum with in-driver retries (not 4 independent load cells)
load_cell_probe.c(shared with hx71x/ADS131M0X) to grow an aggregation state machine for 4 asynchronous streams. MCU-side summing keeps shared code untouched: one clean force stream, as with a single-cell sensor.Testing
BED_MESH_CALIBRATEcampaigns on a 4× HX711 CC1: 10/10 @ 60 °C, 20/20 @ 100 °C, zero faults, zero recoveries needed.load_cell/dump_force) across full macro runs; the new filter was validated offline by replaying 182k captured samples: all 72 phantom crossings removed, all 433 real contact ramps still trigger.Full design record, failure forensics (tap curves, raw stream excerpts), and campaign logs: https://gist.github.com/pdscomp/b3ab4f3c3712ce771f0fa637f643be26
TL;DR / lingo for normal humans
Probe triggered prior to movementerror.