pm-kit: fix jumpy jog (stop drawing mid-spin) - smooth steady spin

The ~1s hitch was the once-per-second readout: show_stats() allocates text
bitmaps (GC pause) and display.refresh() blocks the SPI blit, both stalling the
step loop exactly every second. Now the rate is measured silently while spinning
and the readout (steps + peak) is redrawn only when you release; a gc.collect()
on release + before spinning keeps the heap clean. Steady spin does zero display
work -> smooth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Me Here 2026-06-05 22:13:07 -05:00
parent a7e8061a9b
commit 9651e8bc6a
2 changed files with 14 additions and 11 deletions

View file

@ -122,9 +122,10 @@ The Kit can drive a **physical metronome pendulum**: a 4-input unipolar stepper
powercycle.
- **Jog / test mode** (hold **A + B** at boot): the joystick sets **direction only****L = CCW, R = CW** — and
the motor **accelerates to `STEPPER_MAX_RATE`** (reversing decelerates through zero first), with an onscreen
needle + RGB LED and a **live step counter + rate readout**. *Tuning:* raise `STEPPER_MAX_RATE` until the
motor starts skipping, then back off; if it stalls *starting*, lower `STEPPER_ACCEL` / `STEPPER_JOG_START`.
Powercycle (no buttons) to exit.
needle + RGB LED. The **step count + peakrate readout updates when you release** (drawing midspin would
stall the step loop and make it jumpy, so the spin itself stays glitchfree). *Tuning:* hold to spin, release
to read the peak; raise `STEPPER_MAX_RATE` until the motor skips, then back off; if it stalls *starting*,
lower `STEPPER_ACCEL` / `STEPPER_JOG_START`. Powercycle (no buttons) to exit.
## programs.json

View file

@ -1179,8 +1179,9 @@ class App:
# Joystick = DIRECTION only (no fine speed). Spin at STEPPER_MAX_RATE, reached via an
# acceleration ramp (STEPPER_ACCEL) so the motor doesn't stall trying to start at top speed;
# reversing decelerates through zero, then accelerates the other way.
spin = 0; cur = 0.0; total = 0; win = 0; peak = 0
spin = 0; cur = 0.0; total = 0; win = 0; peak = 0; lastrate = 0
now = time.monotonic(); last = now; tsample = now; tjoy = now
gc.collect() # clean heap before spinning (avoid a GC pause mid-spin)
while True: # no per-iteration sleep: tight step timing in this mode
now = time.monotonic()
if now - tjoy >= 0.004: # control update (joystick + accel), off the step hot-path
@ -1194,15 +1195,16 @@ class App:
elif cur > target: cur = max(0.0, cur - STEPPER_ACCEL * cdt) # decelerate
if cur <= 0.0 and spin != 0 and want != spin: # finished slowing -> stop, or flip direction
if self.pend is not None and self.pend.ok: self.pend.release()
if want == 0: spin = 0; set_needle(0)
else: spin = want; cur = float(STEPPER_JOG_START); set_needle(spin)
if want == 0: # stopped -> now safe to draw the readout + tidy the heap
spin = 0; show_stats(total, lastrate, peak); set_needle(0); gc.collect()
else:
spin = want; cur = float(STEPPER_JOG_START); set_needle(spin)
if spin != 0 and cur > 0.0 and self.pend is not None and self.pend.ok and now - last >= 1.0 / cur:
self.pend.spin(spin > 0); last = now; total += 1; win += 1
if now - tsample >= 1.0: # step-rate readout, once/s (one tiny refresh)
rate = int(win / (now - tsample))
if rate > peak: peak = rate
show_stats(total, rate, peak); win = 0; tsample = now
self.display.refresh()
if now - tsample >= 1.0: # measure rate SILENTLY (drawing here is what hitched it)
lastrate = int(win / (now - tsample))
if lastrate > peak: peak = lastrate
win = 0; tsample = now
# ---------- audio + light ----------
def click(self, level):