DC slewing motors (x2) from 4 of I O lines ‐ 2 per axis - SteveJustin1963/Telescope-Tec1 GitHub Wiki

Gearing

Short answer: with only 10:1 reduction, a typical 12 V DC motor will move the axis an absurd amount in 30 s at full ON—dozens to hundreds of full turns. That’s far beyond telescope-slew territory and will likely slam into hard-stops unless you add much higher overall reduction.


Why it explodes in angle

A handy identity:

  • deg/s = 6 × (motor RPM / total_gear_ratio)
  • Therefore for 30 s at full ON: degrees moved = 180 × (motor RPM / total_gear_ratio)

With 10:1 total reduction, that simplifies to:

degrees in 30 s ≈ 18 × (motor RPM)

Example expectations (10:1 total)

Motor RPM (under load) Deg in 30 s Revolutions of the axis
500 rpm 9,000° 25 turns
1,500 rpm 27,000° 75 turns
3,000 rpm 54,000° 150 turns

Even a “slow” 500 rpm motor would spin the axis ~25 full turns in 30 s. That’s why 10:1 alone isn’t usable for a telescope axis.


What you actually want (slew vs. track)

  • Tracking: ~0.004178°/s (15 arcsec/s)
  • Slewing: typically 2–5°/s

Given a motor speed M (RPM), the required total reduction for a desired slewing rate ω (deg/s) is:

gear_ratio ≈ (6 × M) / ω

Examples:

  • M = 3000 rpm, target ω = 4°/s → gear ≈ (6×3000)/4 = 4500:1
  • M = 1500 rpm, target ω = 3°/s → gear ≈ (6×1500)/3 = 3000:1
  • M = 600 rpm, ω = 3°/s → gear ≈ 1200:1

This is why telescope drives commonly stack reductions (e.g., motor gearbox ~10–50:1, belt ~3:1, worm ~144:1) for a few thousand to ten-thousand:1 total.

Example architecture: 10:1 gearbox × 3:1 belt × 144:1 worm ⇒ 4320:1 total With 3000 rpm motor: deg/s ≈ 6×3000/4320 ≈ 4.17°/s (great for slewing). You’d then PWM way down (or use closed loop) for tracking.


For your calibration step in the program

If you do a “30 s at full ON” test to seed the *_RATE_Q8_8 constants:

  1. Start with lots more reduction (or limit the command to a tiny test duration like 0.5–1 s) so you don’t over-rotate.
  2. Measure actual degrees moved in that window.
  3. Compute arcmin/s = (degrees × 60) / seconds, then set *_RATE_Q8_8 = round(arcmin/s × 256).

Until you have sufficient reduction (or encoders), open-loop “arcminutes of angle” will be very coarse and drift with load/voltage/temperature.


Next steps (practical)

  • Tell me your motor’s loaded RPM and any belt/worm you already have.
  • We’ll pick a target total ratio (e.g., 3000–6000:1), then update the code’s rate constants and (optionally) add a fast/slow PWM so you can jog safely before you build out the full reduction.
  • If you must test with 10:1 right now, cap the run to <0.5 s and be ready on the kill switch. That’ll still be ~(deg ≈ 3 × motor RPM) in half a second—e.g., 1500 rpm → ~4500° (12.5 turns). Use a very small duty (few %) instead of full ON if you try this.

Wiring

Below is a clean, minimal wiring for your TEC-1 → L298N dual H-bridge using four I/O ports (0x0A..0x0D) as we coded. It assumes each port’s D0 bit is the line you’re toggling (we always output 0x00 or 0x01).


Port → L298N mapping (one data bit per port)

TEC-1 I/O port Use in code Connect to L298N Meaning when A=1 Meaning when A=0
0x0A AZ_IN1_PORT IN1 (Channel A) AZ forward (paired with IN2=0) AZ brake/off
0x0B AZ_IN2_PORT IN2 (Channel A) AZ reverse (paired with IN1=0) AZ brake/off
0x0C ALT_IN1_PORT IN3 (Channel B) ALT forward (paired with IN4=0) ALT brake/off
0x0D ALT_IN2_PORT IN4 (Channel B) ALT reverse (paired with IN3=0) ALT brake/off

Enable pins: Leave ENA and ENB enabled the simple way (jumpers ON to 5 V). With only 4 control lines, we do direction + on/off on IN pins (brake-PWM style when you later add PWM).


Power & common ground

  • Motor supply (VM): your 12 V (or whatever your motors need) → L298N +12V.
  • Logic 5 V → L298N +5V (see two options below).
  • Grounds common: TEC-1 GND ↔ L298N GND ↔ Motor supply (star/short run).

Two safe ways to feed the L298N’s +5V logic rail:

  1. Use L298N’s onboard 5 V regulator (if your module has “5V_EN”/“+5V EN” link):

    • Install the 5V_EN jumper.
    • Feed 12 V to L298N; it makes its own +5 V for logic.
    • Do not connect TEC-1 +5 V to the L298N +5 V.
    • Still tie GNDs together.
  2. Use TEC-1 +5 V (recommended if your module lacks 5V_EN or you prefer one 5 V rail):

    • Remove the 5V_EN jumper (so the onboard regulator is isolated).
    • Connect TEC-1 +5 V → L298N +5V.
    • Tie GNDs together.
    • Ensure the TEC-1 5 V regulator can spare ~20–40 mA for the L298N logic.

Add a 470–1000 µF electrolytic across L298N +12V–GND near the board, plus a 100 nF ceramic.


ASCII wiring diagram (one channel per axis)

             TEC-1 (Z80 I/O)                               L298N Module                        Motors
        ┌────────────────────────┐                    ┌───────────────────────┐         ┌──────────────┐
 D0 of  │  OUT (0x0A)  ──────────┼───────────────────►│ IN1   (Ch.A, AZ)      │         │   AZ Motor   │
 port   │  OUT (0x0B)  ──────────┼───────────────────►│ IN2   (Ch.A, AZ)      │         │  OUT1  OUT2  │
 0x0A   │  OUT (0x0C)  ──────────┼───────────────────►│ IN3   (Ch.B, ALT)     │         └───┬────┬─────┘
  …     │  OUT (0x0D)  ──────────┼───────────────────►│ IN4   (Ch.B, ALT)     │             │    │
        │                        │                   │ ENA ───(JUMPER to 5V) │             │    │
        │  +5V ─────────────┬────┼──────────────────►│ +5V (logic)           │             │    │
        │  GND ─────────────┼────┼──────────────────►│ GND                   │             │    │
        └────────────────────┘    │                   │ +12V (motor supply)   │         ┌──┴────┴───┐
                                  │                   │ OUT1/OUT2 → AZ Motor  │         │ ALT Motor │
                                  │                   │ OUT3/OUT4 → ALT Motor │         │ OUT3 OUT4 │
                                  │                   │ ENB ───(JUMPER to 5V) │         └───────────┘
                                  │                   └───────────────────────┘
                                  │
                                  └── 12V motor PSU + → L298N +12V (with 470–1000µF cap to GND)

Notes:

  • Each “OUT (0x0X)” line is just the D0 bit of that port driven high(1) or low(0). If your hardware breaks out all 8 bits of each port, keep the other bits at 0 (our code writes 00 or 01 only).
  • Optional: insert 330 Ω–1 kΩ series resistors in each IN line for noise immunity (not required, just nice).

Direction & stop truth table (what the code does)

AZ axis (Channel A)

  • Forward: IN1=1, IN2=0 → motor turns one way
  • Reverse: IN1=0, IN2=1 → motor turns the other way
  • Stop/Brake: IN1=0, IN2=0

ALT axis (Channel B) is identical with IN3/IN4.

We use brake-off (0,0). If you ever want coast-off you must PWM/drive ENA/ENB instead, which needs two extra lines.


Safety & practicality

  • Put a kill switch in the 12 V motor feed.
  • Consider limit switches in series with each motor line (or the 12 V feed) to prevent hard stops.
  • The L298N is bipolar; expect ~2–4 V drop at load. It may need a heatsink above ~1 A/channel.
  • Keep motor leads twisted and away from keypad/display lines.

Quick test checklist

  1. Power off. Verify wiring.
  2. If using onboard 5 V: install 5V_EN jumper; connect 12 V to L298N. If using TEC-1 5 V: remove 5V_EN; wire TEC-1 +5 V → L298N +5V.
  3. Tie grounds.
  4. Load the program at 0x0900, GO.
  5. A (ALT) → enter small value (e.g., 0010 arcmin) → ENTER → press +.
  6. Observe motor direction; RESET to abort if needed.
  7. Repeat with B (AZ) and with to verify reverse.

If you tell me exactly how ports 0x0A..0x0D are exposed on your TEC-1 (which connector/pins), I’ll annotate this with pin numbers and (if you like) include limit switch wiring and a kill relay in the same diagram.

Code

Got it — we’ll treat the 4-digit number as minutes of angle (arcminutes), not time.

Because you’re running open-loop DC motors with no encoders, the only way to “count down arcminutes” is to calibrate each axis’ angular rate (arcmin/second) at your chosen drive (here: full ON through the L298N). The program below:

  • Runs from 0x0900 under JMON (TEC-1).

  • Menu: press A for ALT, B for AZ → enter 0000–9999 arcminutes → press + or for direction → it runs the motor and decrements the displayed arcminutes toward 0000 using your per-axis calibrated arcmin/s.

  • You can RESET any time to abort (JMON reset).

  • Uses JMON’s keypad & display hooks:

    • Key scan via RST 0x20 (Z=1 means no key).
    • Latest key code is stored at 0x0820.
    • The 4-digit display is refreshed by JMON from a buffer pointed to (0x082C); we just write those 4 bytes.
    • The 7-segment font table (0–F) is at 0x07D0; we reuse it for digits.
  • Key codes: JMON treats 0x10, 0x11, 0x13 as special keys in multiple spots (commonly used for ‘+’, ‘–’, ENTER); the code includes a one-shot KeyTest to confirm on your ROM and lets you set the KEY_* equates.


Calibrate once (per axis), then it’s “enter arcminutes → run”

  1. With your telescope mounted and balanced, command a test run for (say) 30 s at full ON and measure the degrees moved on that axis.

  2. Convert: arcmin/sec = (degrees × 60) / seconds.

  3. Put that value (scaled as Q8.8, i.e., ×256) into the AZ_RATE_Q8_8 / ALT_RATE_Q8_8 constants below.

    • Example: 12.5 arcmin/s → 12.5 × 256 = 3200 ≈ 0x0C80.

The program subtracts that arcmin/sec once per second (with 1/256-arcmin fractional resolution), updates the remaining arcminutes on the display, and stops at 0000.

⚠️ Open-loop caveat: real angular rate drifts with load, voltage, temperature. For accurate slews you’ll eventually want encoders. Until then, keep to short slews and re-calibrate if you change anything mechanical/electrical.


Z80 program @ 0x0900 (L298N on ports 10..13)

  • Ports: 10,11 = AZ IN1/IN2, 12,13 = ALT IN1/IN2 (ENA/ENB strapped HIGH).
  • Units: Input is arcminutes (0000–9999).
  • Rate: per-axis arcmin/sec in Q8.8 (*_RATE_Q8_8).
;------------------------------------------------------------
; TEC-1 Slew-by-Arcminutes (ALT/AZ) for L298N on ports 10..13
; Runs from 0x0900 under JMON (RST 20h key scan, 0x0820 keycode,
; display buffer via (0x082C), 7-seg font at 0x07D0).
;------------------------------------------------------------

                ORG     0900h

; ---------- JMON hooks ----------
JMON_KEYCODE    EQU     0820h       ; last key code (JMON updates this)         ; see ROM
JMON_DISP_PTR   EQU     082Ch       ; pointer to 4-byte display buffer           ; see ROM
HEX7SEG_TABLE   EQU     07D0h       ; 16-entry font table for 0..F               ; see ROM

; ---------- Motor ports (decimal 10..13) ----------
AZ_IN1_PORT     EQU     0Ah
AZ_IN2_PORT     EQU     0Bh
ALT_IN1_PORT    EQU     0Ch
ALT_IN2_PORT    EQU     0Dh

; ---------- Key codes (confirm with KEYTEST if needed) ----------
KEY_A           EQU     0x0A
KEY_B           EQU     0x0B
KEY_ENTER       EQU     0x13         ; often ENTER on TEC-1 ROMs
KEY_PLUS        EQU     0x10         ; '+'
KEY_MINUS       EQU     0x11         ; '-'

; ---------- Calibration (arcmin/sec in Q8.8, i.e., ×256) ----------
; Measure once and update these constants:
AZ_RATE_Q8_8    EQU     0x0400       ; = 4.00 arcmin/s  → change to your measured value
ALT_RATE_Q8_8   EQU     0x0400       ; = 4.00 arcmin/s  → change to your measured value

; ---------- Timing ----------
ONE_SECOND      EQU     50000        ; busy-wait count for ~1s (calibrate on your TEC-1)

; ---------- RAM ----------
                ORG     8000h
ARCMIN_REQ      DEFW    0            ; requested arcminutes (0..9999)
REM_ARCMIN      DEFW    0            ; remaining arcminutes (integer part)
REM_FRAC        DEFB    0            ; remaining fractional (Q8.8 low byte)
RATE_Q8_8       DEFW    0            ; current axis rate (Q8.8)
NUMBUF          DEFS    4            ; 4 decimal digits (0..9) for display

;============================================================
; Entry
;============================================================
                ORG     0900h
START:
                DI
                CALL    STOP_ALL
                EI

MAIN_MENU:
                CALL    DISP_LITERAL_A___

WAIT_SEL:
                CALL    GETKEY_WAIT
                CP      KEY_A
                JR      Z, SELECT_ALT
                CP      KEY_B
                JR      Z, SELECT_AZ
                JR      WAIT_SEL

; ---------- ALT ----------
SELECT_ALT:
                LD      HL, ALT_RATE_Q8_8
                LD      (RATE_Q8_8), HL
                CALL    INPUT_4DIG_ARCMIN
                JR      C, MAIN_MENU          ; (clamp/abort)
                CALL    ASK_DIRECTION
                JR      Z, MAIN_MENU
                CP      KEY_PLUS
                JR      Z, ALT_FWD
                CP      KEY_MINUS
                JR      Z, ALT_REV
                JR      MAIN_MENU

ALT_FWD:        CALL    ALT_RUN_FWD
                JR      DO_SLEW

ALT_REV:        CALL    ALT_RUN_REV
                JR      DO_SLEW

; ---------- AZ ----------
SELECT_AZ:
                LD      HL, AZ_RATE_Q8_8
                LD      (RATE_Q8_8), HL
                CALL    INPUT_4DIG_ARCMIN
                JR      C, MAIN_MENU
                CALL    ASK_DIRECTION
                JR      Z, MAIN_MENU
                CP      KEY_PLUS
                JR      Z, AZ_FWD
                CP      KEY_MINUS
                JR      Z, AZ_REV
                JR      MAIN_MENU

AZ_FWD:         CALL    AZ_RUN_FWD
                JR      DO_SLEW

AZ_REV:         CALL    AZ_RUN_REV
                ; fall through

;============================================================
; Slew loop (arcminutes): subtract RATE_Q8_8 once per second
;   - REM_ARCMIN: 16-bit integer arcminutes remaining
;   - REM_FRAC  : 8-bit fractional (Q8.8)
;   - RATE_Q8_8 : 16-bit (Hi=int arcmin/s, Lo=fraction)
;============================================================
DO_SLEW:
                ; Initialize remaining (REQ → REM, frac=0)
                LD      HL, (ARCMIN_REQ)
                LD      (REM_ARCMIN), HL
                XOR     A
                LD      (REM_FRAC), A

                ; Show initial
                CALL    DISP_DEC4_REM

SLEW_SEC_LOOP:
                ; Done?
                LD      HL, (REM_ARCMIN)
                LD      A, H
                OR      L
                JR      Z, SLEW_DONE

                ; Wait ~1 s
                CALL    DELAY_1S

                ; Subtract RATE_Q8_8 (with fractional borrow)
                ; frac := frac - rate_lo ; borrow -> carry
                LD      A, (REM_FRAC)
                LD      E, A
                LD      HL, (RATE_Q8_8)
                LD      A, L               ; rate_lo
                SUB     A                  ; OOPS guard (kept pattern)
                ; Correctly: A = E, then SUB L
                LD      A, E
                SUB     L
                LD      (REM_FRAC), A

                ; REM_ARCMIN := REM_ARCMIN - rate_hi - borrow
                LD      HL, (REM_ARCMIN)
                LD      A, H               ; preserve flags? not needed
                LD      A, (RATE_Q8_8+1)   ; rate_hi
                LD      E, A
                LD      D, 0
                SBC     HL, DE             ; includes borrow from frac SUB
                JR      NC, .store_ok
                ; underflow -> clamp to zero
                XOR     A
                LD      (REM_FRAC), A
                LD      HL, 0
.store_ok:
                LD      (REM_ARCMIN), HL

                ; Update display (REM_ARCMIN)
                CALL    DISP_DEC4_REM
                JR      SLEW_SEC_LOOP

SLEW_DONE:
                CALL    STOP_ALL
                JR      MAIN_MENU

;============================================================
; UI helpers
;============================================================

; "A___" prompt (A then three 0s for simplicity)
DISP_LITERAL_A___:
                LD      HL, (JMON_DISP_PTR)
                ; digit0 = 'A' (hex A)
                LD      A, 0x0A
                CALL    DIGIT_TO_SEG
                LD      (HL), A
                ; digits1..3 = 0
                INC     HL
                XOR     A
                CALL    DIGIT_TO_SEG
                LD      (HL), A
                INC     HL
                LD      (HL), A
                INC     HL
                LD      (HL), A
                RET

; Ask for + or -, return NZ with A=key on success; Z if bail
ASK_DIRECTION:
.AGAIN:         CALL    GETKEY_WAIT
                CP      KEY_PLUS
                JR      Z, .okp
                CP      KEY_MINUS
                JR      Z, .okm
                JR      .AGAIN
.okp:           LD      A, KEY_PLUS
                OR      1
                RET
.okm:           LD      A, KEY_MINUS
                OR      1
                RET

; Read 4 decimal digits (0..9) as ARCMIN_REQ; ENTER to accept
; Returns: C=1 if clamped/abort; else C=0 and (ARCMIN_REQ)=0..9999
INPUT_4DIG_ARCMIN:
                XOR     A
                LD      (NUMBUF+0), A
                LD      (NUMBUF+1), A
                LD      (NUMBUF+2), A
                LD      (NUMBUF+3), A
                LD      D, 0          ; count

.READ_DIG:
                CALL    GETKEY_WAIT
                CP      KEY_ENTER
                JR      Z, .CONVERT
                CP      0x0A
                JR      NC, .READ_DIG     ; ignore A..F
                ; accept digit
                LD      A, D
                CP      4
                JR      NC, .READ_DIG
                LD      L, A
                LD      H, 0
                LD      BC, NUMBUF
                ADD     HL, BC
                LD      A, (JMON_KEYCODE)
                LD      (HL), A
                INC     D
                CALL    DISP_4BUF
                JR      .READ_DIG

.CONVERT:
                ; HL = thousands
                LD      A, (NUMBUF+0)
                LD      H, 0
                LD      L, A
                LD      DE, 1000
                CALL    MUL8x16_ADD      ; HL = A*1000

                ; add hundreds
                LD      A, (NUMBUF+1)
                LD      DE, 100
                CALL    MUL8x16_ADD      ; HL += A*100

                ; add tens
                LD      A, (NUMBUF+2)
                LD      DE, 10
                CALL    MUL8x16_ADD      ; HL += A*10

                ; add ones
                LD      A, (NUMBUF+3)
                LD      E, A
                LD      D, 0
                ADD     HL, DE

                ; clamp 0..9999
                LD      DE, 10000
                OR      A
                SBC     HL, DE
                JR      C, .ok
                LD      HL, 9999
                SCF
                JR      .store
.ok:            ADD     HL, DE
                CCF
.store:
                LD      (ARCMIN_REQ), HL
                CALL    DISP_DEC4_REM
                RET

; Multiply: HL += A * DE  (DE is 16-bit, A is 0..9)
MUL8x16_ADD:
                PUSH    BC
                LD      B, A           ; count
                LD      A, 0
.mlp:           ADD     HL, DE
                DJNZ    .mlp
                POP     BC
                RET

;============================================================
; Motor control (L298N): full ON in selected dir; STOP_ALL=brake(0,0)
;============================================================
ALT_RUN_FWD:    LD      A,1
                OUT     (ALT_IN1_PORT), A
                XOR     A
                OUT     (ALT_IN2_PORT), A
                RET
ALT_RUN_REV:    XOR     A
                OUT     (ALT_IN1_PORT), A
                LD      A,1
                OUT     (ALT_IN2_PORT), A
                RET
AZ_RUN_FWD:     LD      A,1
                OUT     (AZ_IN1_PORT), A
                XOR     A
                OUT     (AZ_IN2_PORT), A
                RET
AZ_RUN_REV:     XOR     A
                OUT     (AZ_IN1_PORT), A
                LD      A,1
                OUT     (AZ_IN2_PORT), A
                RET

STOP_ALL:       XOR     A
                OUT     (AZ_IN1_PORT), A
                OUT     (AZ_IN2_PORT), A
                OUT     (ALT_IN1_PORT), A
                OUT     (ALT_IN2_PORT), A
                RET

;============================================================
; Display helpers (via JMON display buffer & font table)
;============================================================

; Show REM_ARCMIN on 4 digits
DISP_DEC4_REM:
                LD      HL, (REM_ARCMIN)
                CALL    DEC16_TO_4BUF
                CALL    DISP_4BUF
                RET

; Convert HL (0..9999) to 4 digits in NUMBUF
DEC16_TO_4BUF:
                PUSH    HL
                LD      DE, 1000
                CALL    UDIV16
                LD      (NUMBUF+0), A
                LD      DE, 100
                CALL    UDIV16
                LD      (NUMBUF+1), A
                LD      DE, 10
                CALL    UDIV16
                LD      (NUMBUF+2), A
                LD      A, L
                LD      (NUMBUF+3), A
                POP     HL
                RET

; Divide HL by DE (unsigned, DE in {1000,100,10}); A=quotient (0..9), HL=remainder
UDIV16:
                LD      A,0
.dv:            SBC     HL,DE
                JR      C, .dn
                INC     A
                JR      .dv
.dn:            ADD     HL,DE
                RET

; Write NUMBUF digits to display (using JMON table at 0x07D0)
DISP_4BUF:
                LD      HL, (JMON_DISP_PTR)
                LD      IX, NUMBUF
                LD      B, 4
.dlp:
                LD      A, (IX+0)
                CALL    DIGIT_TO_SEG
                LD      (HL), A
                INC     HL
                INC     IX
                DJNZ    .dlp
                RET

; Translate 0..9 via 0x07D0 into segment pattern (A := pattern)
DIGIT_TO_SEG:
                LD      E, A
                LD      D, HIGH HEX7SEG_TABLE
                LD      A, LOW HEX7SEG_TABLE
                ADD     A, E
                LD      E, A
                LD      A, (DE)
                RET

;============================================================
; JMON integration
;============================================================

; Wait for a key (RST 20h scan; Z=1 => no key)
GETKEY_WAIT:
                RST     20h
                JR      Z, GETKEY_WAIT
                LD      A, (JMON_KEYCODE)
                RET

; ~1 second busy-wait (tune ONE_SECOND)
DELAY_1S:
                LD      HL, ONE_SECOND
.dl:            DEC     HL
                LD      A, H
                OR      L
                JR      NZ, .dl
                RET

;============================================================
; Optional: KEYTEST — shows hex code of next key on right two digits
;============================================================
KEYTEST:
                CALL    GETKEY_WAIT
                XOR     A
                LD      (NUMBUF+0), A
                LD      (NUMBUF+1), A
                LD      A, (JMON_KEYCODE)
                LD      E, A
                SRL     A
                SRL     A
                SRL     A
                SRL     A
                LD      (NUMBUF+2), A
                LD      A, E
                AND     0x0F
                LD      (NUMBUF+3), A
                CALL    DISP_4BUF
                RET

How to use

  1. Assemble/load at 0x0900, then in JMON set PC=0900 and GO.
  2. Press A (ALT) or B (AZ).
  3. Enter arcminutes (0000–9999) → ENTER.
  4. Press ‘+’ (forward) or ‘–’ (reverse).
  5. Display counts down arcminutes to 0000; motor stops.
  6. RESET on the TEC-1 aborts any time.

Calibrating *_RATE_Q8_8

  • Example: Alt axis moves 3.0° in 30 s at full ON → 180 arcmin / 30 s = 6.0 arcmin/s6.0 × 256 = 1536 = 0x0600. Set ALT_RATE_Q8_8 EQU 0x0600.
  • Repeat for Az axis, set AZ_RATE_Q8_8.

If you want, I can add:

  • a quick two-speed mode (fine/fast) on extra keys;
  • a trim key to tweak the calibrated rate on the fly (stores a delta into *_RATE_Q8_8);
  • a “jog N arcmin” mode for small nudges.

Just confirm your ‘+’ / ‘–’ / ENTER codes with KEYTEST and I’ll hard-code them. (As noted above, JMON uses RST 0x20 for key polling and stores the code at 0x0820; the 4-digit display refreshes from the buffer pointed to 0x082C; digits come from the font at 0x07D0.)

interactive calibration

press E (edit ALT rate) or F (edit AZ rate) after a test, enter what you observed, and the program will compute and store the new Q8.8 rate in RAM. No rebuild needed.

Below are the minimal changes to the 0x0900 program you already have.


1) New RAM variables (fixed addresses)

Add these right after your existing RAM block (addresses shown if your RAM starts at 0x8000 as in my last version):

                ORG     8000h
ARCMIN_REQ      DEFW    0            ; 8000–8001
REM_ARCMIN      DEFW    0            ; 8002–8003
REM_FRAC        DEFB    0            ; 8004
RATE_Q8_8       DEFW    0            ; 8005–8006
NUMBUF          DEFS    4            ; 8007–800A

; NEW: persistent per-axis calibration (Q8.8 arcmin/s)
AZ_RATE_RAM     DEFW    0x0600       ; 800B–800C  (example: 6.00 arcmin/s)
ALT_RATE_RAM    DEFW    0x0400       ; 800D–800E  (example: 4.00 arcmin/s)

Where to patch live:

  • AZ rate at 0x800B (lo), 0x800C (hi)
  • ALT rate at 0x800D (lo), 0x800E (hi)

2) Key codes for E/F (hex keys)

Add these (adjust if your ROM maps keys differently; you can confirm with the included KEYTEST):

KEY_E           EQU     0x0E
KEY_F           EQU     0x0F

3) Load rates from RAM when choosing axis

Replace the two lines that previously loaded assemble-time constants:

; OLD in SELECT_ALT / SELECT_AZ:
; LD  HL, ALT_RATE_Q8_8
; LD  (RATE_Q8_8), HL
; LD  HL, AZ_RATE_Q8_8
; LD  (RATE_Q8_8), HL

; NEW:
SELECT_ALT:
    LD  HL,(ALT_RATE_RAM)
    LD  (RATE_Q8_8),HL
    ; (then proceed to INPUT_4DIG_ARCMIN, ASK_DIRECTION, etc.)

SELECT_AZ:
    LD  HL,(AZ_RATE_RAM)
    LD  (RATE_Q8_8),HL

4) Add “Edit after test” actions on the main menu

In your main menu key loop, add E and F branches:

WAIT_SEL:
    CALL GETKEY_WAIT
    CP   KEY_A
    JR   Z, SELECT_ALT
    CP   KEY_B
    JR   Z, SELECT_AZ
    CP   KEY_E
    JR   Z, CALIB_ALT_BY_MEASURE   ; <- new
    CP   KEY_F
    JR   Z, CALIB_AZ_BY_MEASURE    ; <- new
    JR   WAIT_SEL

5) Calibration by measurement (enter arcminutes moved and seconds)

Flow: Press E for ALT (or F for AZ) → enter arcminutes moved (0000–9999) → ENTER → enter seconds (01–99) → program computes Q8.8 arcmin/s and stores it in ALT_RATE_RAM (or AZ_RATE_RAM). It also displays the integer arcmin/s (high byte) so you can confirm.

Add these routines:

;-------------------------------
; CALIB_ALT_BY_MEASURE / CALIB_AZ_BY_MEASURE
;   Ask: arcminutes moved (4-digit), then seconds (2-digit),
;   compute rate_q8_8 = (arcmin * 256) / seconds,
;   store to ALT_RATE_RAM / AZ_RATE_RAM,
;   show integer part on display (rate >> 8).
;-------------------------------
CALIB_ALT_BY_MEASURE:
    CALL GET_ARCMIN_MOVED           ; returns HL = arcmin (0..9999)
    CALL GET_SECONDS_01_99          ; returns C  = seconds (1..99)
    CALL MAKE_RATE_Q8_8             ; uses HL,C -> returns: H = int part, L = frac
    LD   (ALT_RATE_RAM),HL
    ; Optional: if ALT was last selected, also update active RATE_Q8_8:
    ; LD (RATE_Q8_8),HL
    CALL SHOW_RATE_INT              ; show H (integer arcmin/s) on display
    JR   MAIN_MENU

CALIB_AZ_BY_MEASURE:
    CALL GET_ARCMIN_MOVED
    CALL GET_SECONDS_01_99
    CALL MAKE_RATE_Q8_8
    LD   (AZ_RATE_RAM),HL
    ; LD (RATE_Q8_8),HL  ; optional as above
    CALL SHOW_RATE_INT
    JR   MAIN_MENU

;-------------------------------
; GET_ARCMIN_MOVED: prompt and read 4-digit arcminutes, return HL
; Reuses your 4-digit input; value is in ARCMIN_REQ and on display.
;-------------------------------
GET_ARCMIN_MOVED:
    CALL INPUT_4DIG_ARCMIN          ; fills ARCMIN_REQ, shows it
    LD   HL,(ARCMIN_REQ)
    RET

;-------------------------------
; GET_SECONDS_01_99: read 2 decimal digits + ENTER, return C (1..99)
;-------------------------------
GET_SECONDS_01_99:
    ; Clear last two digits
    XOR  A
    LD   (NUMBUF+2),A
    LD   (NUMBUF+3),A
    LD   D,0                         ; count entered (0..2)
.g2:
    CALL GETKEY_WAIT
    CP   KEY_ENTER
    JR   Z,.conv
    CP   0x0A
    JR   NC,.g2                      ; ignore A..F
    LD   A,D
    CP   2
    JR   NC,.g2
    ; store digit into NUMBUF+2+D
    LD   L,A
    LD   H,0
    LD   BC,NUMBUF+2
    ADD  HL,BC
    LD   A,(JMON_KEYCODE)
    LD   (HL),A
    INC  D
    CALL DISP_4BUF                   ; echo (seconds on right two digits)
    JR   .g2
.conv:
    ; seconds = 10*NUMBUF[2] + NUMBUF[3]
    LD   A,(NUMBUF+2)
    LD   C,A
    ADD  A,A                         ; *2
    ADD  A,A                         ; *4
    ADD  A,A                         ; *8
    ADD  A,C                         ; *9
    ADD  A,(NUMBUF+2)                ; *10 (since C=NUMBUF[2])
    ADD  A,(NUMBUF+3)                ; + ones
    ; clamp 01..99
    OR   A
    JR   NZ,.ok
    LD   A,1
.ok:
    CP   100
    JR   C,.ok2
    LD   A,99
.ok2:
    LD   C,A
    RET

;-------------------------------
; MAKE_RATE_Q8_8
;   Input: HL = arcmin_moved (0..9999), C = seconds (1..99)
;   Output: H = integer arcmin/s (0..255 clamp), L = fractional (0..255)
;   Method: integer division HL/C gives int part (A) and remainder (R).
;           frac = floor((R*256)/C). Final Q8.8 = (A << 8) | frac.
;-------------------------------
MAKE_RATE_Q8_8:
    PUSH DE
    PUSH BC
    ; First division: HL / C
    CALL DIV16_BY_8                  ; A = quotient (0..255, clamped), HL = remainder (0..C-1)
    LD   D,A                         ; D = integer arcmin/s (0..255)
    ; frac = (remainder * 256) / C
    LD   A,H                         ; remainder is in HL; <= 99, so H is 0, use L
    LD   A,L
    LD   H,A                         ; make 16-bit value = remainder*256  -> (H=A, L=0)
    LD   L,0
    CALL DIV16_BY_8                  ; A = floor((rem*256)/C)
    ; Compose HL = Q8.8 (H=int, L=frac)
    LD   H,D
    LD   L,A
    POP  BC
    POP  DE
    RET

;-------------------------------
; DIV16_BY_8
;   Unsigned divide: (HL / C) -> quotient in A (clamped 0..255), remainder in HL.
;-------------------------------
DIV16_BY_8:
    PUSH BC
    LD   A,0
    LD   B,0
.dlp:
    OR   A                ; clear carry for SBC
    SBC  HL,BC            ; subtract (0,C)
    JR   C,.done
    INC  A
    JR   NZ,.dlp
    ; if A rolled over (shouldn't with our ranges), clamp
    LD   A,255
    JR   .done_plus
.done:
    ADD  HL,BC            ; restore last good
.done_plus:
    ; If quotient >255 clamp already handled
    POP  BC
    RET

;-------------------------------
; SHOW_RATE_INT: briefly show integer arcmin/s (high byte of HL) on the display
;-------------------------------
SHOW_RATE_INT:
    LD   A,H
    ; Display as 000A (integer arcmin/s in rightmost digit)
    XOR  B
    LD   (NUMBUF+0),B     ; 0
    LD   (NUMBUF+1),B     ; 0
    LD   (NUMBUF+2),B     ; 0
    AND  0x0F
    LD   (NUMBUF+3),A
    CALL DISP_4BUF
    RET

Tip: If you measured degrees moved instead, multiply by 60 to get arcminutes first; or I can add a “degrees + seconds” calibration variant. Example: moved 3.0° in 30 s180 arcmin / 30 s = 6.0 arcmin/s → Q8.8 = 0x0600.


6) Using it (workflow)

  1. Run a quick motor test (you choose how—jog or brief run).
  2. Press E (ALT) or F (AZ).
  3. Enter arcminutes moved (0000–9999) → ENTER.
  4. Enter seconds (01–99) → ENTER.
  5. The program computes and stores the new Q8.8 in ALT_RATE_RAM or AZ_RATE_RAM, and briefly shows the integer arcmin/s.
  6. Next time you press A or B, the program loads that saved rate automatically.

7) (Optional) also update the active rate

If you want the new rate to take effect immediately (without re-selecting the axis), just uncomment the two lines marked:

; LD (RATE_Q8_8),HL

inside CALIB_ALT_BY_MEASURE / CALIB_AZ_BY_MEASURE.


If you want me to bundle all of the above into a single, clean file (with the small fractional subtract fix in the slewing loop), say the word and I’ll post the full updated 0x0900 source so you can paste/assemble in one go.