pm-kit: jog stops promptly on release (drop the decel ramp)

Releasing the joystick used to ramp the speed down through zero (~0.3s), so the
motor coasted after release. Decel isn't needed (only fast *starts* stall), so
release now stops immediately; keep the gentle accel ramp on start. Also only
rewrite the PIO clock when the rate actually changes (no 100Hz redundant writes).

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

View file

@ -121,8 +121,8 @@ The Kit can drive a **physical metronome pendulum**: a 4-input unipolar stepper
`stepper_accel`, `stepper_jog_start`, `pend_swing_deg`, `stepper_steps_per_rev`) — edit in editor mode, `stepper_accel`, `stepper_jog_start`, `pend_swing_deg`, `stepper_steps_per_rev`) — edit in editor mode,
powercycle. powercycle.
- **Jog / test mode** (hold **A + B** at boot): the joystick sets **direction only****L = CCW, R = CW** — and - **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 the motor **accelerates to `STEPPER_MAX_RATE`** (gentle ramp on start so it doesn't stall; releasing stops it
needle + RGB LED and a **live step count + rate readout**. The step pulses are generated by **PIO + DMA** promptly, no coasting), with an onscreen needle + RGB LED and a **live step count + rate readout**. The step pulses are generated by **PIO + DMA**
(hardwaretimed on a state machine), so the motor stays smooth even while the screen redraws — there's no CPU (hardwaretimed on a state machine), so the motor stays smooth even while the screen redraws — there's no CPU
step loop to stall. *Tuning:* hold to spin; raise `STEPPER_MAX_RATE` until the motor skips, then back off; if step loop to stall. *Tuning:* hold to spin; 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. it stalls *starting*, lower `STEPPER_ACCEL` / `STEPPER_JOG_START`. Powercycle (no buttons) to exit.

View file

@ -1237,7 +1237,7 @@ class App:
self.display.refresh() self.display.refresh()
# Joystick = DIRECTION only. The CPU just ramps the commanded rate (accel) and sets the PIO # Joystick = DIRECTION only. The CPU just ramps the commanded rate (accel) and sets the PIO
# clock; the motor steps autonomously. Reversing decelerates through zero, then the other way. # clock; the motor steps autonomously. Reversing decelerates through zero, then the other way.
spin = 0; cur = 0.0; total = 0.0; peak = 0 spin = 0; cur = 0.0; total = 0.0; peak = 0; rset = -1
now = time.monotonic(); tctl = now; tsample = now now = time.monotonic(); tctl = now; tsample = now
gc.collect() gc.collect()
while True: while True:
@ -1246,19 +1246,17 @@ class App:
cdt = now - tctl; tctl = now cdt = now - tctl; tctl = now
dx = self.jx.value - center dx = self.jx.value - center
want = (1 if dx > 0 else -1) if abs(dx) > JOY_DEADZONE else 0 want = (1 if dx > 0 else -1) if abs(dx) > JOY_DEADZONE else 0
if spin == 0 and want != 0: # start from rest at the safe pull-in rate if want == 0: # released -> stop promptly (no coasting)
if spin != 0:
st.off(); spin = 0; cur = 0.0; rset = -1; set_needle(0); gc.collect()
elif want != spin: # (re)start in the requested direction at the pull-in rate
spin = want; cur = float(STEPPER_JOG_START); st.run(want > 0); set_needle(spin) spin = want; cur = float(STEPPER_JOG_START); st.run(want > 0); set_needle(spin)
target = float(STEPPER_MAX_RATE) if (spin != 0 and want == spin) else 0.0 rset = int(cur); st.set_rate(cur)
if cur < target: cur = min(target, cur + STEPPER_ACCEL * cdt) # accelerate else: # holding -> accelerate up to top speed
elif cur > target: cur = max(0.0, cur - STEPPER_ACCEL * cdt) # decelerate if cur < STEPPER_MAX_RATE:
if spin != 0: cur = min(float(STEPPER_MAX_RATE), cur + STEPPER_ACCEL * cdt)
if cur <= 0.0 and want != spin: # finished slowing -> stop, or flip direction total += cur * cdt; peak = max(peak, int(cur))
st.off() if int(cur) != rset: rset = int(cur); st.set_rate(cur) # only rewrite the clock when it changes
if want == 0: spin = 0; set_needle(0); gc.collect()
else: spin = want; cur = float(STEPPER_JOG_START); st.run(want > 0); set_needle(spin)
else:
st.set_rate(cur if cur > 1.0 else 1.0)
total += cur * cdt; peak = max(peak, int(cur))
if now - tsample >= 0.5: # LIVE readout - safe now: the motor runs from PIO/DMA if now - tsample >= 0.5: # LIVE readout - safe now: the motor runs from PIO/DMA
show_stats(int(total), int(cur), peak); tsample = now; self.display.refresh() show_stats(int(total), int(cur), peak); tsample = now; self.display.refresh()