pm-kit: jog mode = direction-only, accelerate to a smooth max speed
The joystick has no useful fine speed control, so jog now treats it as direction only and runs the motor at STEPPER_MAX_RATE, reached via a trapezoidal accel ramp (STEPPER_ACCEL from STEPPER_JOG_START) so it doesn't stall trying to start at top speed; reversing decelerates through zero then accelerates the other way. Default top rate set to a realistic 600 half-steps/s for the 28BYJ-48; tune via jog. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5f9e9dfad7
commit
19d646a873
2 changed files with 41 additions and 36 deletions
|
|
@ -112,13 +112,16 @@ The Kit can drive a **physical metronome pendulum**: a 4-input unipolar stepper
|
|||
drives the screen graphic exactly and the motor.
|
||||
- `STEPPER_STEPS_PER_REV` — your motor's half‑steps per full turn (28BYJ‑48 half‑step ≈ 4096); maps
|
||||
degrees → steps.
|
||||
- `STEPPER_MAX_RATE` — fastest half‑steps/sec the motor can follow; the swing **auto‑shrinks** (rather than
|
||||
desync) when a beat is too short to sweep the full arc. At wide swings/fast tempi the screen still shows
|
||||
the full angle while the arm does what it physically can.
|
||||
- **Jog / test mode** (hold **A + B** at boot): the joystick spins the motor **L = CCW, R = CW** (speed by
|
||||
how far you push), with an on‑screen direction needle + RGB LED and a **live step counter + rate readout**.
|
||||
Push to full deflection and note the **peak rate** where the motor begins to stall, then set
|
||||
`STEPPER_MAX_RATE` just below it. Power‑cycle (no buttons) to exit.
|
||||
- `STEPPER_MAX_RATE` — top half‑steps/sec the motor sustains smoothly. **Jog mode spins at this rate**, and
|
||||
the pendulum **auto‑shrinks** its arc (rather than desync) when a beat is too short to sweep the full angle.
|
||||
- `STEPPER_ACCEL` — ramp (half‑steps/sec²) used to reach top speed without stalling; lower it if the motor
|
||||
stalls/buzzes when starting.
|
||||
- `STEPPER_JOG_START` — jog kickoff rate from rest (keep at or below the motor's pull‑in rate).
|
||||
- **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 on‑screen
|
||||
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`.
|
||||
Power‑cycle (no buttons) to exit.
|
||||
|
||||
## programs.json
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,9 @@ STEPPER_ENABLED = True # set False if no motor is wired (the pins just sta
|
|||
PEND_SWING_DEG = 120 # total swing arc, end-to-end, in degrees - drives BOTH the screen graphic and the arm
|
||||
STEPPER_STEPS_PER_REV = 4096 # your motor's half-steps per full 360 turn (28BYJ-48 half-step ~4096); maps deg -> steps
|
||||
STEPPER_ARC = round(STEPPER_STEPS_PER_REV * PEND_SWING_DEG / 360.0) # half-steps for one end-to-end swing
|
||||
STEPPER_MAX_RATE = 900 # max half-steps/sec the motor can follow; auto-shrinks the arc at fast tempi
|
||||
STEPPER_MAX_RATE = 600 # top half-steps/sec the motor sustains smoothly (jog spins here; tune via jog mode)
|
||||
STEPPER_ACCEL = 1800 # half-steps/sec^2 ramp so it reaches top speed without stalling (lower if it stalls)
|
||||
STEPPER_JOG_START = 150 # jog kickoff half-steps/sec from rest (keep <= the motor's pull-in rate)
|
||||
|
||||
# ----- pins (fixed by the EP-0172 board) -----
|
||||
P_SCK, P_MOSI, P_CS, P_DC, P_RST = board.GP2, board.GP3, board.GP5, board.GP6, board.GP7
|
||||
|
|
@ -1138,7 +1140,7 @@ class App:
|
|||
self.g_overlay.append(rect(0, 40, WIDTH, HEIGHT - 40, C_BG)) # cover the normal UI
|
||||
for s, fnt, col, yy in (("STEPPER JOG TEST", FONT_M, C_CYAN, 64),
|
||||
("Joystick L = CCW R = CW", FONT_S, C_TXT, 96),
|
||||
("push further = faster", FONT_S, C_DIM, 114),
|
||||
("spins at max speed (ramped)", FONT_S, C_DIM, 114),
|
||||
("power-cycle (no buttons) to exit", FONT_S, C_DIM, 132)):
|
||||
tg, w, h = make_text(s, fnt, col, C_BG); tg.x = 12; tg.y = yy; self.g_overlay.append(tg)
|
||||
self.g_overlay.append(rect(PEND_PX - 26, PEND_PY + 4, 52, 4, C_PANEL)) # stand
|
||||
|
|
@ -1157,37 +1159,37 @@ class App:
|
|||
show_stats(0, 0, 0)
|
||||
self.display.refresh()
|
||||
time.sleep(0.1); center = self.jx.value
|
||||
total = 0; win = 0; peak = 0; cw = True; dt = 1.0; lastdir = None
|
||||
now = time.monotonic(); last = now; tsample = now; tjoy = now
|
||||
while True: # no per-iteration sleep: tight step timing in this mode
|
||||
now = time.monotonic()
|
||||
if now - tjoy >= 0.004: # poll the joystick ~250x/s, OFF the step hot-path
|
||||
tjoy = now
|
||||
dx = self.jx.value - center; mag = abs(dx)
|
||||
if mag > JOY_DEADZONE:
|
||||
cw = dx > 0
|
||||
frac = (mag - JOY_DEADZONE) / (32768 - JOY_DEADZONE)
|
||||
if frac > 1.0: frac = 1.0
|
||||
dt = 0.02 + (0.0006 - 0.02) * frac # commanded up to ~1600 steps/s (find the motor's real wall)
|
||||
if cw != lastdir: # direction changed -> LED + needle (rare, so refresh here)
|
||||
lastdir = cw
|
||||
if cw: self.led.set(0, 150, 0)
|
||||
else: self.led.set(0, 0, 255)
|
||||
ang = PEND_THETA if cw else -PEND_THETA
|
||||
bx = PEND_PX + int(PEND_LEN * math.sin(ang)); by = PEND_PY - int(PEND_LEN * math.cos(ang))
|
||||
def set_needle(d): # d = +1 CW (green), -1 CCW (blue), 0 centre (off)
|
||||
if d > 0: self.led.set(0, 150, 0); a = PEND_THETA
|
||||
elif d < 0: self.led.set(0, 0, 255); a = -PEND_THETA
|
||||
else: self.led.set(0, 0, 0); a = 0.0
|
||||
bx = PEND_PX + int(PEND_LEN * math.sin(a)); by = PEND_PY - int(PEND_LEN * math.cos(a))
|
||||
bob.x = bx; bob.y = by
|
||||
arm.points = [(PEND_PX - 4, PEND_PY), (PEND_PX + 4, PEND_PY), (bx, by)]
|
||||
self.display.refresh()
|
||||
elif lastdir is not None: # back to center -> stop, release, recentre needle
|
||||
lastdir = None; dt = 1.0
|
||||
# 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
|
||||
now = time.monotonic(); last = now; tsample = now; tjoy = now
|
||||
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
|
||||
cdt = now - tjoy; tjoy = now
|
||||
dx = self.jx.value - center
|
||||
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
|
||||
spin = want; cur = float(STEPPER_JOG_START); set_needle(spin)
|
||||
target = float(STEPPER_MAX_RATE) if (spin != 0 and want == spin) else 0.0
|
||||
if cur < target: cur = min(target, cur + STEPPER_ACCEL * cdt) # accelerate
|
||||
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()
|
||||
self.led.set(0, 0, 0)
|
||||
bob.x = PEND_PX; bob.y = PEND_PY - PEND_LEN
|
||||
arm.points = [(PEND_PX - 4, PEND_PY), (PEND_PX + 4, PEND_PY), (PEND_PX, PEND_PY - PEND_LEN)]
|
||||
self.display.refresh()
|
||||
if lastdir is not None and self.pend is not None and self.pend.ok and now - last >= dt:
|
||||
self.pend.spin(lastdir); last = now; total += 1; win += 1
|
||||
if now - tsample >= 1.0: # readout once/s (one tiny refresh, not 3+ hitches/s)
|
||||
if want == 0: spin = 0; set_needle(0)
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue