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:
- 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.
- Measure actual degrees moved in that window.
- 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:
-
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.
-
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
00or01only). - 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
- Power off. Verify wiring.
- 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.
- Tie grounds.
- Load the program at 0x0900, GO.
- A (ALT) → enter small value (e.g., 0010 arcmin) → ENTER → press +.
- Observe motor direction; RESET to abort if needed.
- 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 scan via
-
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”
-
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.
-
Convert: arcmin/sec = (degrees × 60) / seconds.
-
Put that value (scaled as Q8.8, i.e., ×256) into the
AZ_RATE_Q8_8/ALT_RATE_Q8_8constants below.- Example: 12.5 arcmin/s →
12.5 × 256 = 3200 ≈ 0x0C80.
- Example: 12.5 arcmin/s →
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
- Assemble/load at 0x0900, then in JMON set PC=0900 and GO.
- Press A (ALT) or B (AZ).
- Enter arcminutes (0000–9999) → ENTER.
- Press ‘+’ (forward) or ‘–’ (reverse).
- Display counts down arcminutes to 0000; motor stops.
- 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/s →
6.0 × 256 = 1536 = 0x0600. SetALT_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 s → 180 arcmin / 30 s = 6.0 arcmin/s → Q8.8 = 0x0600.
6) Using it (workflow)
- Run a quick motor test (you choose how—jog or brief run).
- Press E (ALT) or F (AZ).
- Enter arcminutes moved (0000–9999) → ENTER.
- Enter seconds (01–99) → ENTER.
- The program computes and stores the new Q8.8 in
ALT_RATE_RAMorAZ_RATE_RAM, and briefly shows the integer arcmin/s. - 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.