TITLE FLEXER vers. 3.04 by Martin Eberhard
SUBTTL Banner
;======================================================
;FLEXER: Floppy Drive Exercizer
;for CDOS 2.X and a Cromemco 4FDc, 16FDC or 64FDC
;
;For measuring and adjusting floppy drives, including:
; 8" and 5.25" (minidisk) drives
; Stepper-motor and voice-coil actuators
; 40-track minidisks and 80-track "HD" minidisks
; Single- and double-sided disks
; Single- and double-density drives
;
;=================
;Revision History
;1.01 - 1.06 27FEB2013 - 13MAR2013 M. Eberhard
; Created, basic functionality
;
;2.00 - 2.04 14MAR2013 - 13JUN2013 M.Eberhard
; Add read/write and buffer commands, etc.
; Add buffer and sector commands. Add ID command. Fix
;
;3.00 17 February 2016 M. Eberhard [NEVER RELEASED]
; Streamlined user interface, mainly by creating
; immediate commands
;
;3.01 31 January 2017 M. Eberhard
; Cleanup, debug
;
;3.02 21 February 2019 M. Eberhard
; Add "A" and "V" automatic commands to SK and SE
;
;3.03 14 June 2020 M. Eberhard
; Fix bug in GET01
;
;3.04 2 August 2020 M. Eberhard
; Wait for -Seek Complete to go high before waiting
; for it to go low during both seeks and restores.
; (for PerSci 299B drives). Change coding style so
; that registers a,b,c,d,e,h,l,sp,fa are lowercase.
; Tidy up comments a tad. Default drive tipe=8.
;
;3.05 30 January 2021 M. Eberhard
; Report "voicecoil" instead of the step rate when a
; voicecoil drive is selected. Fix timeout value in
; voicecoil seek.
VERSION equ 0305h ;hex-encoded-decimal
;=====================================================
;Written to assemble (under CDOS) with Cromemco's ASMB
;assembler or cross-assemble (under CP/M) with SLR
;Systems's Z80ASM assembler (Release 1.32)
;
;Notes:
;
;This program uses software timing loops for seek and
;rotational time measurements, and assumes:
;1) a 4 MHz Z-80 with 0-wait state memory
;2) Accessing the FDC DSTAT register inserts 1 wait
; state (which is imposed by the Z-80 itself)
;3) Accessing the FDC ASTAT register inserts 4 wait
; states, including the one imposed by the Z-80.
;
;Do not change 'jp' instructions within timing loops
;to 'jr' instructions, as this will effect the timing.
;
;Because of the timing loops, interrupts are blocked
;while this program runs. There is an assembly option
;(INTIO) to block interrupt just during timing loops.
;If you set INTIO to be TRUE, then you must set the
;16FDC's interrrupt mask register correctly: mask the
;disk interrupts, and either mask or allow UART
;interrupts as your software requires. Note that this
;code has not been tested in an interrupt environment.
;
;This program requires about 16K of RAM, starting at
;100h. The last 10.5K of this RAM is a buffer that is
;only used during the Write Pattern function.
;
;The 4/16FDC unloads the disk head by de-selecting the
;drive. This means that any disk operation that
;requires the drive to be selected must also do so
;with its head loaded. For example, the Eject function
;applies to the selected disk. If the disk heads are
;unloaded and you use the EJ command, the head will
;load on the selected disk before the disk ejects.
;
;The WP (write pattern) command writes an entire track
;of the specified data pattern, without any sector
;formatting. A track like this is useful for tuning
;the drive's read channel. A disk that has been used
;for this purpose must be reformatted (using e.g. CDOS
;INIT) before it can be used by CDOS.
EJECT
SUBTTL System Equates
FALSE equ 0
TRUE equ 0FFH
;Program Equates
INTIO equ FALSE ;TRUE allows ints except during
;timing loops. Make sure 16FDC
;ints are set up right.
;(TRUE is still untested.)
MAXTK8 equ 76 ;default 8" disk max track #
MAXTKM equ 39 ;default minidisk max track #
SEEKTO equ 5 ;approx. seek timeout in seconds
DRTRYS equ 3 ;default read retry max
MAXBPS equ 1024 ;max bytes per sector
TBSIZE equ 10500 ;track buffer size, bytes
;ASCII Characters
CTRLC equ 03H
BS equ 08H
LF equ 0AH
CR equ 0DH
ESC equ 1Bh
QUOTE equ 27H ;single quote
DEL equ 7FH
;CDOS 2.X Addresses
BASE equ 0 ;CDOS base addr
WBOOT equ BASE ;warm-boot to CDOS
CDOS equ BASE+5 ;CDOS System Call
FCB equ BASE+5CH ;CDOS file ctrl blk
COMBUF equ BASE+80H ;disk & cmd line buff
USAREA equ BASE+100H ;User program
CBSIZE equ USAREA-COMBUF ;cmd line buff size
;CDOS 2.X System Call Functions
CCONIN equ 80h ;Read Console to a, no echo
CCONIE equ 1 ;Read console to a, echo
CCONOT equ 2 ;Write E to Console
CPRINT equ 9 ;Print $-terminated line at de
CRDCON equ 10 ;Input buffered line at de
CCONST equ 11 ;Console input status
;16FDC Ports & Bits
ASTAT equ 04H ;Aux stat input
ACTRL equ 04H ;Auxiliary cmd output
DSTAT equ 30H ;Status input
DCMMD equ 30H ;cmd output
DTRCK equ 31H ;Track I/O
DSCTR equ 32H ;Sector I/O
DDATA equ 33H ;Data I/O
DFLAG equ 34H ;Flags input
DCTRL equ 34H ;Control output
EJECT
;WD1793 Commands (For DCMMD port)
RSTRCM equ 00000000B ;(I)Restore
SEEKCM equ 00010000B ;(I)Seek
STEPCM equ 00100000B ;(I)Step
STPICM equ 01000000B ;(I)Step In
STPOCM equ 01100000B ;(I)Step Out
RSECCM equ 10000000B ;(II)Read Record
WSECCM equ 10100000B ;(II)Write Record
RADRCM equ 11000000B ;(III)Read addr
RTRKCM equ 11100000B ;(III)Read track
WTRKCM equ 11110000B ;(III)Write track
FINTCM equ 11010000B ;(IV)Force int
;WD1793 Command Options
RS03MS equ 00000000B ;(I) 3 mS step rate
RS06MS equ 00000001B ;(I) 6 mS step rate
RS10MS equ 00000010B ;(I) 10 mS step rate
RS15MS equ 00000011B ;(I) 15 mS step rate
VERTRK equ 00000100B ;(I) Verify on dest track
HDLOAD equ 00001000B ;(I) Head Load request
UPDTTR equ 00010000B ;(I) Update track reg
A0DDM equ 00000001B ;(III)Write deleted-data mark
F1SCF equ 00000010B ;(II) Side compare flag
F2SSF equ 00001000B ;(II) Side select flag
MULTCM equ 00010000B ;(II) R/W Multiple
ED15MS equ 00000100B ;(II & III) Settling Delay
;Composite WD1793 commands (Step rates added later)
;Note: Disk will always appear ready if not HDLOAD
DSTEPI equ STPICM+UPDTTR+HDLOAD ;step in
DSTEPO equ STPOCM+UPDTTR+HDLOAD ;step out
DSEEK equ SEEKCM+HDLOAD ;seek
WTRACK equ WTRKCM+ED15MS ;write track
DEFSTP equ RS10MS ;default step rate
GETID equ RADRCM+ED15MS ;Get track ID
RDSECT equ RSECCM+ED15MS ;read sector
WRSECT equ WSECCM+ED15MS ;write sector
;DCTRL output bits
DCDS0 equ 00000001B ;Drive Sel 0
DCDS1 equ 00000010B ;Drive Sel 1
DCDS2 equ 00000100B ;Drive Sel 2
DCDS3 equ 00001000B ;Drive Sel 3
DCSMSK equ 11110000B ;Drive Sel mask
DCMAXI equ 00010000B ;0=Mini, 1=Maxi
DCMOTO equ 00100000B ;Motor On
DCMMSK equ 11011111B ;Motor mask
DCDDEN equ 01000000B ;Enable Double Density
DCAUTO equ 10000000B ;Enable auto-wait
;ACTRL output bits (Active-low)
ACSID0 equ 00000010B ;-Side select
ACCTRL equ 00000100B ;-Control Out
ACRSTR equ 00001000B ;-Restore
ACFSEK equ 00010000B ;-Fast Seek
ACDSO equ 00100000B ;-Drive Select Override
ACEJCT equ 01000000B ;-EJECT
;Composite ACTRL values
EJECT equ not[ACSID0+ACEJCT+ACDSO]
FASEEK equ not[ACSID0+ACFSEK+ACDSO]
DRESTR equ not[ACSID0+ACRSTR+ACDSO]
ACSSM equ not[ACSID0+ACDSO]
;WD1793 DSTAT input bits, after a command
SBUSY equ 00000001B ;(All)Busy
SINDEX equ 00000010B ;(I)Index
SDATRQ equ 00000010B ;(II & III)Data Request
STRAK0 equ 00000100B ;(I)Track 0
SLOSTD equ 00000100B ;(II & III)Lost Data
SCRCER equ 00001000B ;(ALL)CRC Error
SRNFER equ 00010000B ;(II & III)Record Not Found
SHEADL equ 00100000B ;(I)Head Loaded
SWFALT equ 00100000B ;(Writes)Write Fault
SRTYPE equ 00100000B ;(Read rec) Record Type
SWPROT equ 01000000B ;(All)Disk Write Protect
SNORDY equ 10000000B ;(All)Not Ready
;DFLAG input bits
DFSEOJ equ 00000001B ;Disk cmd complete
DFAWTO equ 00000010B ;Auto-wait timeout
DFSMTO equ 00000100B ;Motor timeout
DFSMON equ 00001000B ;WD1793 requesting motors on
DSFhLD equ 00100000B ;WD1793 requesting head load
DSFDRQ equ 10000000B ;WD1793 is requesting data
;ASTAT bits
DASSIP equ 01000000B ;Seek in progress
DASDRQ equ 10000000B ;Data Request
EJECT
SUBTTL Main Loop
ORG USAREA
if not INTIO
di ;block ints always
endif
;Clear RW, verify buffers using CDOS's stack
ld bc,MAXBPS*2 ;clear 2 buffers
ld e,0
call FILBUF
;Create local stack, print banner
ld de,BANNER ;Sign-on msg
;Fix stack, Print message at de, and go to MAIN
PRMAIN: ld sp,STACK ;SP=local stack
call CRPRNT
;========================================
;Main loop: re-initialize several things,
;look for commands from user & dispatch
;========================================
MAIN: call REDCTL ;restore DCTL state
;may spin the motor up
if INTIO
ei ;allow I/O ints
endif
ld a,(DTYPE) ;cancel forced-step
and not FRCSTP
ld (DTYPE),a
ld a,(SIDENO) ;get side #
or ACSSM ;(re)set ACTRL
out ACTRL,a
MAINLP: ld de,PROMPT
call CRPRNT
call GETLIN ;get 0-terminated line
jr Z,MAINLP ;null line: try again
;Search for matching 2-chr command in CMDTAB
ld de,CMDTAB-1 ;point to command table
ld c,(hl) ;1st command chr in C
inc hl ;2nd cmd chr at (hl)
NXTCOM: inc de ;skip over address
ld a,(de)
or a ;test for table end
jr Z,CMDERR ;cmd not in table
xor c ;test first character
ld b,a ;temp save result
inc de
ld a,(de) ;2nd table character
xor (hl) ;test 2nd character
or b ;both characters match?
inc de ;point to addr low byte
ld a,(de) ;Low byte of address
ld b,a
inc de ;point to addr hi byte
jr NZ,NXTCOM ;NO match: keep looking
ld a,(de) ;high byte of address
ld d,a
ld e,b ;low byte of address
inc hl ;point past command
;de=command execution address
;hl points past command on command line
call SKIPSP ;Skip any trailing spaces
;Command found. Dispatch to (de)
;with Z set if end of cmd line
ld bc,MAIN ;create return addr
push bc
push de ;execution addr
ret ;go execute
;-----------------------------------
;Bogus command return (resets stack)
;-----------------------------------
CMDERR: ld de,HUHST ;'Huh?'
jr PRMAIN ;print msg & go to main
EJECT
SUBTTL Command Execution Routines
;******===Command Routine==========
;* ID * Display Track ID
;******
;On Entry:
; Z set if end of cmd line already
; hl=address of 1st chr aftercmd
;==================================
FTKID: jr NZ,CMDERR ;no params allowed
call TRAKID ;get track id
;Display track ID info
push bc ;save SPT & retries
push hl ;save BPS
ld de,TKIDST ;heading
call CRPRNT
ld de,TRAKST ;print track
call CRPRNT
ld a,(COMBUF) ;track # from ID
call PDEC8
ld de,SIDEST ;print side
call CRPRNT
ld a,(COMBUF+1) ;side # from ID
call PDEC8
call PDENS ;density
call PCRLF
pop hl ;recover bytes/sector
call PDEC16 ;print hl as dec
ld de,BPSST ;Bytes per sector
call PRINTF
call PCRLF
pop bc ;SPT, retries fm TRAKID
ld a,b ;a=SPT
ld de,UNSUST ;unsupported
or a ;0 means unsupported
jp Z,PRMAIN
call PDEC8 ;print a=SPT
ld de,SPTST ;sector/track
jp PRINTF
EJECT
;******===Command Routine==========
;* SR * Read Sector
;******
;On Entry:
; Z set if end of cmd line already
; hl=address of 1st chr aftercmd
;==================================
FREAD: call RWSTUP
ld de,SECBUF ;target address
call READS ;read the sector
ld de,RERRST ;read error?
jp NZ,RWFLT
push bc ;c=final status
ld de,SRRCST ;read required
ld c,b ;tries remaining
call PRTRYS ;print retries, if any
call PCRLF
call PDEC16
ld de,BYRDST ;bytes trans, rec typ
call PRINTF
pop bc ;c=final status
ld a,c
ld de,PREST ;present
and SRTYPE ;deleted data mark?
jr NZ,FREAD1
ld de,ADELST ;absent
FREAD1: call PRINTF
;Fall into CHKSID
;===Subroutine=========================
;Compare SIDENO to side found by TRAKID
;On Entry:
; SIDENO=current side
; (COMBUF+1)=TRAKID side #
;On Exit:
; Z set if match
; message printed, Z clear if not
;Trashes a,b,de
;======================================
CHKSID: ld a,(SIDENO) ;correct side?
rrca ;make it 0/1
xor 1 ;0 for side 0
ld b,a
ld a,(COMBUF+1) ;ID side #
cp b ;mismatch?
ret Z ;N:done
ld de,SDMMST
call CRPRNT ;Y:report
inc b ;clear Z
ret
EJECT
;******===Command Routine==========
;* SW * Write Sector
;****** with read-back verify
;On Entry:
; Z set if end of cmd line already
; hl=address of 1st chr aftercmd
;==================================
FWRITE: call RWSTUP ;setup for write
call CHKSID ;side mismatch?
ret NZ ;Y:done
push hl ;save bytes/sect
ld de,ASKSST
call ASKYN ;ready?
call PREPD ;autowait, etc.
ld de,SECBUF ;source address
ld a,(WDDM) ;deleted data mark?
or WRSECT ;WD1793 command
if INTIO
di ;no ints during write
endif
out DCMMD,a
;Write hl bytes to disk from memory at de
WRITDL: in a,DFLAG ;(11)autowait
ld a,(de) ;(7)
out DDATA,a ;(12)disk data
inc de ;(6)
dec hl ;(6)
ld a,h ;(4)
or l ;(4)
jp NZ,WRITDL ;(10)
call EOJ ;get final status
ld a,c ;Final status
ld de,PROTST
and SWPROT
jr NZ,FLTEXT
ld a,c
ld de,WFLTST ;write fault
and SRNFER+SCRCER+SLOSTD+SWFALT
jr NZ,RWFLT
;Report number of bytes written
call PCRLF
pop hl ;Bytes/Sect
push hl ;save for verify
call PDEC16 ;number of bytes
ld de,BYWRST ;bytes written
call PRINTF
;Read back into verify buffer, to verify write
pop hl ;Bytes/Sect
jr WVERFY ;reuse verifycmd
EJECT
;******===Command Routine==========
;* SV * Verify Sector
;******
;On Entry at FVERFY:
; Z set if end of cmd line already
; hl=address of 1st chr aftercmd
;Entry at WVERFY:
; hl=byte count
;==================================
FVERFY: call RWSTUP ;setup for read
;return hl=byte count
call CHKSID ;side mismatch?
ret NZ ;Y:done
WVERFY: push hl ;byte count for compare
ld de,VERBUF ;dest addr=verify buf
call READS ;read sector we wrote
ld de,RVERST ;read err during verify
jr NZ,FLTEXT ;...even after retries?
ld de,VRRCST ;verify read required
ld c,b ;c=tries remaining
call PRTRYS ;print retries, if any
;Compare read-back data to written data
ld de,VERBUF ;target address
ld hl,SECBUF ;source address
pop bc ;bc=bytes/sector
push bc ;save for report
VLOOP: ld a,(de) ;loop through bc bytes
inc de
cpi
jr NZ,VFAIL
jp NV,VLOOP ;no jr instr. for NV
;--------------
;verify success
;--------------
call PCRLF
pop hl ;Bytes/Sect
call PDEC16 ;number of bytes
ld de,BYVFYD ;bytes verified
jp PRINTF
;-----------
;verify fail
;-----------
VFAIL: ld de,VERRST ;verify error
jr FLTEXT
;-----------------
;read/Write error
;de=error string
;-----------------
RWFLT: and SLOSTD+SCRCER+SWFALT
jr NZ,FLTEXT
ld de,RNFST ;record not found
FLTEXT: jp PRMAIN
EJECT
;******===Command Routine==========
;* VD * Display the Verify Buffer
;******
;On Entry:
; Z set if end of cmd line already
; hl=address of 1st chr aftercmd
;==================================
FVDISP: ld hl,VERBUF ;verify buffer addr
jr BDISP ;same as FBDISP
;******===Command Routine==========
;* BD * Buffer Display
;******
;On Entry at FBDISP:
; Z set if end of cmd line already
; hl=address of 1st chr aftercmd
;On Entry at BDISP:
; hl=buffer address
;==================================
FBDISP: ld hl,SECBUF ;read/write buffer addr
BDISP: jr NZ,JCMDER ;no params allowed
ld de,0 ;address count
FDISP1: call PADDR ;print address
push hl ;save for ASCII print
push de
FDISP2: call PSPACE
;Print 16 bytes in hex
ld a,(hl) ;data
call PRHEX
inc de ;next
inc hl
ld a,e
and 0Fh ;new line every 16
jr NZ,FDISP2
;Print them again in ASCII
call PSPACE
pop de
pop hl
FDISP3: ld a,(hl)
cp ' ' ;non-printing?
jr C,FDISP4
cp DEL ;more non-printing
jr C,FDISP5
FDISP4: ld a,'.'
FDISP5: call PRINTA
inc de ;next
inc hl
ld a,e
and 0Fh ;new line every 16
jr NZ,FDISP3
ld a,e
or a ;pause every 256
jr NZ,FDISP1
ld a,d
cp MAXBPS/256 ;max bytes/sec
ret NC
push de
ld de,SCONST ;space to continue
call CRPRNT
pop de
FDISP6: call CHKCON ;Abort from user?
cp ' ' ;space to advance?
jr NZ,FDISP6 ;n: keep waiting
jr FDISP1
EJECT
;******===Command Routine==========
;* BF * RW Buffer Fill
;******
;On Entry:
; Z set if end of cmd line already
; hl=address of 1st chr aftercmd
;==================================
FFILL: call GETHEX ;get fill value in de
ld a,b ;overflow?
cp 3 ;max 2 digits
jr NC,JCMDER
ld a,(hl) ;any more chrs?
or a
jr NZ,JCMDER ;junk at end
ld bc,MAXBPS ;size of RW buffer
;Fall into FILBUF
;===Subroutine=================
;Fill SECBUF
;On Entry:
; bc=number of bytes to fill
; e=fill value
;Trashes a,bc,hl
;==============================
FILBUF: ld hl,SECBUF
FILLUP: ld (hl),e ;loop to fill buffer
inc hl
dec bc
ld a,b
or c
jr NZ,FILLUP
ret
;-----------------
;Command error:
;make use of jr's
;-----------------
JCMDER: jp CMDERR
EJECT
;******===Command Routine==========
;* BE * RW Buffer Edit
;******
;allow user to edit buffer contents
;On Entry:
; Z set if end of cmd line already
; hl=address of 1st chr aftercmd
;==================================
FEDIT: call GETHEX ;get address in de
ld a,b ;overflow?
cp 5
jr NC,JCMDER
ld a,(hl) ;any more chrs?
or a
jr NZ,JCMDER
ld a,d
cp MAXBPS/256 ;past buffer end?
jr NC,JCMDER
FEDIT0: ld hl,SECBUF
add hl,de ;hl points to buff addr
FEDIT1: call PADDR ;print address
FEDIT2: call PSPACE
ld a,(hl) ;data
call PRHEX
ld a,'.' ;separator
call PRINTA
push de
push hl
call GETLIN ;get user input
jr NC,FEDIT3 ;Just did immediate cmd?
pop hl ;did immediate command
pop de ;..so reprint address
jr FEDIT1
FEDIT3: call GETHEX ;convert to hex
;Z set if no input
ld a,e ;get low byte
pop hl
pop de
jr Z,FEDIT4 ;no change?
ld (hl),a ;update w/ low byte
FEDIT4: inc hl
INC de
ld a,e ;need an address?
and 07H ;every 8
jr NZ,FEDIT2
ld a,d ;end of sector?
cp MAXBPS/256
jr C,FEDIT1
ld de,0 ;sector addr count
jr FEDIT0 ;wrap to beginning
EJECT
;******===Command Routine==========
;* SE * Display Settings & Status
;******
;On Entry:
; Z set if end of cmd line already
;==================================
FSET: jr NZ,JCMDER ;no parameters allowed
ld de,FSETST
call CRPRNT
call PUNIT ;unit
ld a,(SDCTRL) ;any unit selected?
and 0Fh
ret Z ;N:done
call PDTYP ;type, max track, rates
call PMOTOR ;motor state
call PSIDE ;side
call PTRACK ;current track
call PHLOAD ;head-load state
;Issue a seek command so that the WD1793 will send
;protect and index to DSTAT. Also select drive, since
;unloading the head is done by deselecting the drive.
call NULSEK ;returns DSTAT in C
;The Ready bit will always read high for minidisks, and
;will also always read high once the 1792 stops
;requesting head load. Therefore, the ready state of
;DCTRL is captured during NULSEK.
ld a,(SDCTRL) ;no ready signal
and DCMAXI ;...on a minidisk
ld de,CRLFST ;no ready status message
jr Z,DS1
ld de,DRIVST ;Drive
call CRPRNT
ld de,RDYST ;ready
ld a,c
and SNORDY
jr Z,DS1
ld de,NRDYST ;not ready
DS1: push af ;save ready status
call PRINTF
ld de,DISKST ;Disk
call CRPRNT
ld de,UNPRST ;not write protected
in a,DSTAT
and SWPROT
jr Z,DS2
ld de,PROTST ;write protected
DS2: call PRINTF
ld de,TRK0ST ;track 0
call CRPRNT
ld de,DETST ;detected
in a,DSTAT
and STRAK0
jr NZ,DS3
ld de,NDETST ;not detected
DS3: call PRINTF
pop af ;ready status
call Z,IMEASR ;measure spindle if ready
call PMODE ;operating mode
call PVERB ;verbose mode
jp PRETRY ;Read retries
;The disk appears ready. Print status that depend on
;having a disk spinning in the drive
EJECT
;******===Command Routine==============
;* A= * Select Automatic or Manual Mode
;******
;On Entry:
; hl=addr of parameter value
;On Exit:
; AUTOSK updated
;======================================
FSETA: call GET01 ;0 or 1
ld (AUTOSK),a
;Fall into PMODE
;===Subroutine=======
;Print Operating Mode
;====================
PMODE: ld de,MODEST ;'Mode:'
call CRPRNT
ld a,(AUTOSK)
ld de,aUTOST ;'Automatic'
or a ;0 means auto
jr NZ,PMOD1
ld de,MANST ;'Manual'
PMOD1: jp PRINTF
EJECT
;******===Command Routine==========
;* D= * Set Density
;******
;On Entry:
; hl=addr of parameter value
;On Exit:
; SDCTRL drive density bit updated
;==================================
FSETD: call GETASC ;get ASCII param
ld hl,SDCTRL
cp 'S'
jr Z,SETSD
cp 'D'
jp NZ,CMDERR ;illegal value?
ld a,(hl)
or DCDDEN ;double density
jr SETD1
SETSD: ld a,(hl)
and not DCDDEN ;single density
SETD1: ld (hl),a
;Fall into PDENS
;===Subroutine=========
;Print selected density
;======================
PDENS: ld a,(SDCTRL)
ld de,SNGLST ;'Single'
and DCDDEN ;what density?
jr Z,DENS1
ld de,dUBLST ;'Double'
DENS1: call CRPRNT
ld de,DENSST ;'density'
jp PRINTF ;print value & ret
EJECT
;******===Command Routine====
;* M= * Set Max Track number
;******
;On Entry:
; hl=addr of parameter value
;On Exit:
; MAXTRK updated
;============================
FSETM: ld bc,99 ;default & max
call GETDEC
or a ;0 not allowed
jp Z,CMDERR
ld (MAXTRK),a
;Fall into PMAXTK
;===Subroutine=========
;Print max track number
;======================
PMAXTK: ld de,MAXTST ;'Max Track:'
call CRPRNT
ld a,(MAXTRK)
jp PDEC8
EJECT
;******===Command Routine====
;* N= * Set max Read Retries
;******
;On Entry:
; hl=addr of parameter value
;On Exit:
; RTRIES updated
;============================
FSETN: ld bc,99 ;default & max
call GETDEC
inc a ;tries=retries+1
ld (RTRIES),a
;Fall into PRETRY
;===Subroutine============
;Print max allowed retries
;=========================
PRETRY: ld de,RETRST ;'Retries:'
call CRPRNT
ld a,(RTRIES)
dec a ;retries=tries-1
jp PDEC8
EJECT
;******===Command Routine====
;* R= * Set Step-Rate
;******
;On Entry:
; hl=addr of parameter value
;On Exit:
; STPRAT updated
;============================
FSETR: ld bc,3 ;default & max
call GETDEC
ld (STPRAT),a
;Fall into PSTPRT
;===Subroutine===========
;Print selected Step Rate
;========================
PSTPRT: ld de,STRST ;'Step Rate:'
call CRPRNT
ld a,(STPRAT)
ld hl,STPTAB ;look up actual rate
ld e,a
ld d,0
add hl,de
ld b,(hl) ;get step rate
ld a,(DTYPE) ;minidisk/HD mini?
or a ;voicecoil drive?
jr z,PSTR2 ;y: say so
cp DTMD
ld a,b ;recover step rate
jr C,PSTR1
rlca ;*2 for minidisks
PSTR1: call PDEC8
ld de,MSST ;mS
jr PSTR3
PSTR2: ld de,VCST
PSTR3: jp PRINTF ;return from there
EJECT
;******===Command Routine====
;* S= * Set Disk Side
;******
;On Entry:
; hl=addr of parameter value
;On exit:
; SIDENO=00 for drive 1,
; 02 for drive 0 (for ACTRL)
;============================
FSETS: call GET01
rlca ;bit in place for ACTRL
xor 02 ;reverse polarity
ld (SIDENO),a
or ACSSM ;now set side
out ACTRL,a
;Fall into PSIDE
;===Subroutine===========
;Print selected disk side
;Trashes a,bc,de
;========================
PSIDE: ld de,SIDEST ;'Side:'
call CRPRNT
ld a,(SIDENO)
rrca ;bit in low position
xor 1 ;it was inverted
jp PDEC8
EJECT
;******===Command Routine====
;* T= * Select Drive Type
;******
;On Entry:
; hl=addr of parameter value
;On Exit:
; DTYPE updated
; SDCTRL updated
; MAXTRK updated
;============================
FSETT: call GETASC ;get 1 ASCII param
ld de,MAXTK8*256 ;e=type, d=max track#
cp 'V'
jr Z,SETT1
inc e
cp '8'
jr Z,SETT1
ld de,MAXTKM*256+2
cp '5'
jp NZ,CMDERR
SETT1: ld a,d ;save max track #
ld (MAXTRK),a
ld hl,SDCTRL ;clear DCTRL maxi bit
ld a,(hl)
and not DCMAXI
ld (hl),a
ld a,e
ld (DTYPE),a
cp DTMD ;8" or voicecoil 8"?
jr NC,SETT2 ;N: its mini
ld a,(hl)
or DCMAXI ;set SDCTRL maxi bit
ld (hl),a
SETT2:
;Fall into PDTYP
;===Subroutine=====================
;Print Drive Type, Max Track, Rates
;==================================
PDTYP: ld de,DTYPST ;'Disk type:'
call CRPRNT
ld a,(DTYPE)
or a
jr NZ,PDTYP0
ld de,VCST ;'Voicecoil '
call PRINTF
ld a,1
PDTYP0: ld de,MAXIST ;'8"'
dec a
jr Z,PDTYP1
ld de,MINIST ;'Minidisk'
PDTYP1: call PRINTF
call PMAXTK ;max track
call PDENS ;density
call PDRATE ;data rate
jp PSTPRT ;...and step rate
EJECT
;******===Command Routine==========
;* U= * Set Unit Number {A-D}
;******
;On Entry:
; hl=addr of parameter value
;On Exit:
; SDCTRL drive select bits updated
; The drive has been restored to 0
;==================================
FSETU: call GETASC ;get ASCII param
sub 'A' ;Adjust to 0
cp 4 ;max drive letter is D
jp NC,CMDERR ;illegal value
ld b,a ;make drive selectcmd
inc b
xor a
scf
SULOOP: rla ;shift bit into place
djnz SULOOP
ld b,a
ld hl,SDCTRL
ld a,(hl) ;previous DCTRL value
and DCSMSK ;unit mask
or b ;new unit number
ld (hl),a ;new DCTRL value
call FRCST ;temp force stepping
call RESTOR ;select and restore
;Fall into PUNIT
;===Subroutine======
;Print selected unit
;===================
PUNIT: ld de,UNITST ;'Unit:'
call CRPRNT
;Fall into PRDRIV
;===Subroutine=================
;Print the current drive letter
;Trashes a,c,de
;==============================
PRDRIV: ld a,(SDCTRL)
and 0Fh ;just get drive bits
ld de,NONEST
jp Z,PRINTF ;print none if none
ld c,'A'-1
PU1: rrca ;loop to create A-D
inc c
jr NC,PU1
ld a,c ;a='A'-'D'
jp PRINTA
EJECT
;******===Command Routine====
;* V= * Set Verbose Mode
;******
;On Entry:
; hl=addr of parameter value
;On Exit:
; VERBOS updated
;============================
FSETV: call GET01
ld (VERBOS),a
;Fall into PVERB
;===Subroutine=====
;Print Verbose Mode
;==================
PVERB: ld de,VERBST ;'Verbose'
call CRPRNT
ld a,(VERBOS)
ld de,OFFST ;'off'
or a ;0 means disabled
jr Z,PVERB1
ld de,ONST ;'on'
PVERB1: jp PRINTF
EJECT
;******===Command Routine==========
;* RE * Restore to track 0
;******
;On Entry:
; Z set if end of cmd line already
; hl=address of 1st chr aftercmd
;==================================
FRESTR: jp NZ,CMDERR ;No parameters allowed
call RESTOR
jp OPTRAK ;print track # and ret
;*****==============
;* ? * Help Command
;*****==============
FHELP: ld de,HLPMSG
jp PRMAIN ;print msg & go to main
EJECT
;******===Command Routine==================
;* EJ * Eject (PerSci) disk
;******
;Select the correct drive and side
;Send 1-sec eject pulse per Persci 277 spec
;On Entry:
; Z set if end of cmd line already
; hl=address of 1st chr aftercmd
;==========================================
FEJECT: jp NZ,CMDERR ;no parameters allowed
ld a,(SDCTRL) ;select the drive
out DCTRL,a ;maybe been motor-off
ld hl,SIDENO
ld a,(hl) ;select the side
or a,EJECT ;combine ejectcmd
out ACTRL,a ;eject now
call STALL1 ;stall for 1 sec
ld a,(hl) ;select the side
or a,ACSSM
out ACTRL,a ;end eject pulse
ld de,EJST ;Ejected
jp CRPRNT
EJECT
;******===Command Routine======================
;* WP * Write Pattern
[...]
;******
;Write a complete track of the given pattern.
;There are no sectors - this is not a formatted
;track. This function is for tuning the read
;channel, etc.
;On Entry:
; Z set if end of cmd line already
; Unit, side, track, density are all set
; hl=address of 1st chr aftercmd
;==============================================
FWPAT: jp Z,CMDERR ;must be at least 1
;Get pattern values into COMBUF. Pattern values
;between F5 and FE are not allowed, as they will kick
;the WD1793 into one of its modes.
ld de,COMBUF ;reuse buffer
ld b,0FFH ;count goes 1 extra
GEPLUP: inc b ;bump pattern count
push bc
push de
call GETHEX ;val in de, cnt in B
ld a,b ;too many digits?
cp 3
jp NC,CMDERR ;Y:abort
ld a,e ;get low byte
pop de
ld (de),a ;save byte in list
inc de ;bump pointer
push de
ld de,BADPST ;can't be F5-FE
inc a ;must be below F6 now
cp 0F6H
jp NC,PRMAIN
pop de
ld a,b ;hex digit count
pop bc ;recover pattern count
or a ;0 digits last time?
jr NZ,GEPLUP ;N:keep looking
;fill track buffer with the pattern. This may over-
;fill by (COMBUF-4)/2 bytes, but buffer is big enough
ld a,b ;pattern count
ld bc,TBSIZE ;bytes to write
ld de,TRKBUF ;dest
FILLP: push af ;save pattern count
ld hl,COMBUF ;source
FILLP1: ldi ;(hl)+:=(de)+,bc-
dec a
jr NZ,FILLP1
ld a,b ;done?
rlca ;msb means underflow
jr C,FLDON
pop af ;pattern count
jr FILLP
FLDON: ;pattern count on stack
;Print the relevent settings, and confirm with user
ld de,WPATST ;write pattern
call CRPRNT
call PUNIT ;unit
call PTRACK ;current track
call PSIDE ;side
call PDENS ;density
ld de,PLENST ;pattern length
call CRPRNT
pop bc ;pattern count
ld a,b
call PDEC8
ld de,ASKWST ;warning...
call ASKYN ;ask Y or N
;Prepare to write the track with pattern
call PREPD ;get ready to write
ld b,DFSEOJ+DFAWTO ;test mask
ld sp,TRKBUF ;data pointer now
ld a,WTRACK ;kick off write
if INTIO
di ;no ints during write
endif
out DCMMD,a
jr WTLENT
;b=DFLAG mask to check for done or timeout
;c=data port address
;SP points to track data in buffer. (SP gets
; repaired at the end by PRMAIN.)
;Loop through the pattern, quickly writing to disk
;until the controller stops requesting data, due to
;finding the next index pulse. This may write one
;bytes after the WD1793 stops requesting data, which
;will be ignored by the WD1793.
;Worst-case out-to-out time is 16 uS, or 64 cycles
;This loop worst case out-to-out=38 cycles
WTLOOP: in a,DFLAG ;(11)autowait until DRQ
out (c),d ;(13)low byte
WTLENT: pop de ;(10)get 2 bytes
in a,DFLAG ;(11)autowait until DRQ
out (c),e ;(13)
and b ;(4) done or timeout?
jr Z,WTLOOP ;(10)
call EOJ ;Read final status
ld de,PROTST ;write protected
ld a,c
and SWPROT
jr NZ,WTDONE ;quit if protected
ld de,DONEST
ld a,c
and SWFALT+SLOSTD
jr Z,WTDONE ;no errors: done
ld de,WFLTST ;write fault
WTDONE: jp PRMAIN ;repairs stack too
EJECT
;******===Command Routine==========
;* MO * Motor Off/On
;****** toggle with speed check
;On Entry:
; Z set if end of cmd line already
;On Exit:
; SDCTRL is updated
;==================================
FMOTOR: jp NZ,CMDERR ;no value allowed
ld b,0 ;no toggle the 1st time
call NULSEK ;expose INDEX
MOT2: ld a,(SDCTRL) ;get SDCTRL w/ motor
xor b ;space: toggle it
call DODCTL ;...now
call PMOTOR ;report motor state
MOT3: call IMEASR ;measure spindle motor
call CHKCON ;Abort from user?
cp ' ' ;space to advance?
jr NZ,MOT3 ;n: keep waiting
ld b,DCMOTO ;toggle now
jr MOT2 ;y: toggle motor
EJECT
;******===Command Routine===================
;* ST * Step [...]
;******
;Step to or between given tracks.
;Same as the SKcmd ,except the WD1793 fast-
;seek mode is disabled.
;On Entry:
; Z set if end of cmd line already
; hl=address of 1st chr aftercmd
;Note: head must be loaded to step, since
;drive is selected only when head is loaded.
;===========================================
FSTEP: call FRCST ;temp force stepping
;Fall into FSEEK
EJECT
;******===Command Routine============================
;* SK * Seek [...trkn]
;******
;Fast seek sequentially through the list of specified
;tracks, repeating the list until the user types 'Q'.
;If no list is specified, then use list from
;previous SK or ST command. If just one track is
;specified, then just seek that track and quit.
;On Entry:
; Z set if end of cmd line already
; DTYPE=0 for fast-seeks, <>0 for steps
; hl=address of 1st chr aftercmd
;Note: head must be loaded to step, since the drive
;is selected only when the head is loaded.
;====================================================
;Get complete list of target tracks, convert
;them to binary, and store them in PATBUF.
FSEEK: ld de,PATBUF ;pattern buffer
push de
ld a,(de) ;in case reuse pattern
ld c,a
jr Z,FSEEK2 ;reuse prior pattern?
ld c,0FFh ;value cnt, 1 extra
GELUP: inc c ;inc C
push bc ;save count
call SKIPSP ;skip ' ' between vals
ld a,(MAXTRK) ;max value
ld c,a ;...for GETDEC
call GETDEC ;get a track #
;Z set if done
inc de ;bump pointer
ld (de),a ;save in list
pop bc
jr NZ,GELUP ;keep looking
FSEEK2: call SELECT ;select specified unit
pop hl ;PATBUF
ld (hl),c ;pattern cnt in buffer
;..first location
;If more than 1 track, then loop thru the list until
;the user types CTRLC or ESC. If just 1 track, then
;seek that track and quit. If 0 target tracks, then
;seek track 0 and quit.
;hl=PATBUF
SEKAGN: ld a,(hl) ;1st byte:pattern count
cp 2 ;0 or 1 tracks?
jr C,SEEK1 ;Y: seek just 1 track
;Seek tracks listed in PATBUF
ld b,a ;pattern count
push hl ;PATBUF
SEKLUP: inc hl
ld a,(hl) ;get a track number
push hl
push bc
call SEEK ;Also prints seek time
;..if verbose
;Check for user input between seeks
SUWAIT: call CHKCON ;user trying to quit?
;check for toggling automatic/manual mode or verbose
;mode
ld c,a ;save user input
ld hl,VERBOS ;point to flags
cp 'V' ;verbose mode toggle?
jr NZ,SKNOTV
ld a,(hl) ;y: toggle verbose mode
xor 1
ld (hl),a
SKNOTV:
inc hl ;point to AUTOSK
cp 'A' ;automatic mode toggle?
ld a,(hl) ;a=automatic-mode flag
jr NZ,SKNOTA
xor 1 ;y: toggle auto mode
ld (hl),a ;..and put it back
SKNOTA:
or a ;manual or automatic?
jr NZ,SKNUI ;auto: all done with user
;Manual mode: check for any of the other immediate
;commands
ld a,c ;recover user input
call CKIMED ;immediate command?
cp ' ' ;space to advance?
jr NZ,SUWAIT ;n: keep waiting
;Next pattern entry
SKNUI: pop bc
pop hl
djnz SEKLUP ;loop thru B entries
;Restart pattern
pop hl ;PATBUF
jr SEKAGN ;restart sequence
;Just seek one track, report the track, and quit
SEEK1: inc hl
ld a,(hl) ;get track no.
;Fall into SEEK
EJECT
SUBTTL Subroutines
;===Subroutine==============================
;Seek target track & wait for done. Use fast
;(voice-coil) seek if possible. Print seek
;time if verbose mode enabled.
;Timeout after ~12 Sec
;On Entry
; DTYPE is 0 for fast-seeking, NZ otherwise
; a=target track
; SIDENO=selected side
; SDCTRL is valid
;Trashes a,bc, hl
;===========================================
SEEK: out DDATA,a ;target track to WD1793
if INTIO
di ;no interrupts here
endif
ld a,(SDCTRL) ;select the drive
out DCTRL,a ;may have been heads-up
ld a,(DTYPE) ;can we fast-seek?
or a ;0 means yes
jr NZ,SKSLO
ld a,(SIDENO) ;enable fast-seek mode
or FASEEK
out ACTRL,a
SKSLO:
ld a,(STPRAT) ;current step rate
or DSEEK ;combine w/ seekcmd
out DCMMD,a ;kick off WD1793
push de ;(11)
ld de,2 ;(10)2 for 20 uS bump
ld hl,4 ;(10)account for 61 uS
ld c,h ;(4)upper counter byte
ld b,11 ;(7) 61 uS out-to-in
;..(56 uS min) as
S28LUP: djnz S28LUP ;(13*10+8) ..required
;..by the WD1793)
;Wait for WD1793 to finish seek command. This loop
;takes exactly 80 cycles=20 uS per pass (assuming 1
;wait state to read DSTAT, due to the Z80 CPU), so
;de=2. This loop will timeout when c times out. c is
;in units of 655360 uS=0.6536 seconds, or about 21/32
;sec the funny math for SEEKTO causes the result to be
;rounded up.
SEK0: add hl,de ;(11)Bump timer
ld a,c ;(4)
adc a,d ;(4)catch carry (d=0)
ld c,a ;(4)high byte done
cp (SEEKTO*32+20)/21 ;(7)timeout?
jr Z,SEKTO ;(7)Y: abort
inc de ;(6)stall
dec de ;(6)stall
or a ;(4)stall
in a,DSTAT ;(10+1W)wait for
rrca ;(4)test SBUSY
jr C,SEK0 ;(12/7)
;Done seeking if no voicecoil. 79 cycles in-to-in
;exactly.
ld a,(DTYPE) ;(13)fast-seeking?
or a ;(4)0 means yes
jr NZ,SEKDON ;(7)N:done
;Wait for voice-coil drive to indicate seek in
;progress, or timeout and fall into the SEK2 loop in
;1 mS. (PerSci 299B drives take as much as 600 uS to
;raise its -Seek Complete after it sees -Step.) This
;loop takes exactly 80 cycles=20 uS per pass (with 4
;wait states to read ASTAT), so de=2.
;Register b is used as a timeout for this loop.
ld b,50 ;(7)1 mS timeout
SEK1: add hl,de ;(11)add 20 uS to timer
ld a,c ;(4)
adc a,d ;(4)catch carry (d=0)
ld c,a ;(4)high byte done
in a,ASTAT ;(10+4W)wait for SIP
or a ;(4)stall
or a ;(4)stall
and DASSIP ;(7)1=seek in progress
jr NZ,SEK2 ;(7/12)
or a ;(4)stall
or a ;(4)stall
djnz SEK1 ;(13/8)timeout?
;Wait for voice-coil drive to indicate seek complete.
;This loop takes exactly 80 cycles=20 uS per pass
;(with 4 wait states to read ASTAT), so de=2. This
;loop will timeout when c times out.
SEK2: add hl,de ;(11)add 20 uS to timer
ld a,c ;(4)
adc a,d ;(4)catch carry (d=0)
ld c,a ;(4)high byte done
cp (SEEKTO*32+20)/21 ;(7)timeout?
jr Z,SEKTO ;(7)Y: abort
in a,ASTAT ;(10+4W)wait SIP end
ret Z ;(5)stall (Z not set)
ret Z ;(5)stall
and DASSIP ;(7)1=seek in progress
jr NZ,SEK2 ;(12)
;Done. c,hl=elapsed time in units of 10 uS.
SEKDON: pop de
ld a,(SIDENO) ;disable fast-seek mode
or ACSSM
out ACTRL,a
if INTIO
ei ;interrupts ok
endif
;Print the current track number, and
;also the seek speed only if VERBOS<>0
;On Entry:
; c,hl=seek time in 0.01 mS units
; VERBOS <> 0 enables printing
ld a,(VERBOS)
or a
ret Z
ld de,SKTST ;seek time:
call CRPRNT
call PRNTMS ;print seek time
jr PTRACK
;Seek timeout. Print timeout message based on whether
;or not the drive type is set for voicecoil drive
SEKTO: POP de ;fix stack
ld de,VCST ;'voicecoil '
ld a,(DTYPE) ;was it a voicecoil sk?
or a
call Z,PRINTF
ld de,SKTOST
SEKTO1: jp PRMAIN
EJECT
;===Subroutine=================
;Print the current track number
;Trashes all registers
;==============================
PTRACK: ld de,TRAKST ;Track:
call CRPRNT
in a,DTRCK ;get actual track #
;Fall into PDEC8
;===Subroutine====================
;Print a as decimal on the console
;Trashes all registers
;=================================
PDEC8: ld l,a
ld h,0
;fall into PDEC16
;===Subroutine==================================
;Entry at PRFRAC prints 2 digits (for after a .)
;Print hl as decimal on the console
;Trashes all registers
;===============================================
PDEC16: ld d,0 ;Suppress leading 0's
ld bc,-10000
call DECDIG
ld bc,-1000
call DECDIG
ld bc,-100
call DECDIG
PRFRAC: ld bc,-10
call DECDIG
ld a,l ;last digit is simple
jr DECDG0 ;with leading 0's
;---Local Subroutine--------------------------
;Divide hl by power of 10 in bc, and print
;the result, unless it's a leading 0.
;On Entry:
; hl=Dividend
; bc=divisor (a negative power of 10)
; d=0 if all prior digits were 0
;On Exit:
; Quotent is printed, unless it's a leading 0
; hl=remainder
; d=0 iff this and all prior digits are 0
;---------------------------------------------
DECDIG: ld a,-1 ;will go 1 too many times
push de ;d=leading zero state
DIGLP: ld d,h ;de gets prev value
ld e,l
inc a
add hl,bc ;subtract power of 10
jr C,DIGLP
ex de,hl ;hl has remainder
pop de ;d=leading 0 state
ld e,a ;E has digit to print
or d ;leading 0 to suppress?
ret Z ;Y: digit is done
ld d,a ;save for next digit
ld a,e ;recover new digit
DECDG0: add '0' ;make ASCII
jp PRINTA
EJECT
;===Subroutine===================
;Get track ID
;On Exit:
; (COMBUF)=track #
; (COMBUF+1)=Side #
; (COMBUF+2)=sector number found
; (COMBUF+3)=Sector length (0-3)
; b=calculated sectors/track
; hl=calculated bytes/sector
; DCDEN bit in SDCTRL set/reset
; according to track density
;Rude abort if error reading ID
;Trashes c,de
;================================
TRAKID: ld a,(RTRIES) ;how many retries?
rlca ;*2 for density
ld b,a ;retry count
TKID0: call PREPD ;set up for R/W
ld a,GETID
ld hl,6 ;6-byte ID
ld de,COMBUF
call READD ;read the ID
ld hl,SDCTRL
ld a,c ;final status
and SRNFER+SCRCER+SLOSTD
jr Z,TKIDGD ;good read?
;retry on error with other density
ld a,(hl)
xor DCDDEN ;try other density
ld (hl),a
djnz TKID0 ;retry if b<>1
ld de,IDERST ;error reading ID
jp PRMAIN ;print exit
;Good read. Compute retries remaining in C
TKIDGD: ld a,b ;how many tries?
inc a ;cy clear from above
rra ;account for density
ld c,a ;remaining retries
;Compute sectors/track & bytes/sector for ret
ld a,(COMBUF+3) ;BPS from ID
ld b,a ;b=BPS value
ld a,(hl) ;SDCTRL
and DCMAXI ;compute disk size offset
rrca ;put DCMAXI into bit 2
rrca
add b ;offset based on DCMAXI
ld e,a ;de becomes offset
ld d,0
ld a,(hl) ;SDCTRL
and DCDDEN ;offset on density
jr NZ,TKID1
inc de ;bump if SD
TKID1: ld hl,SPTTAB ;table beginning
add hl,de ;offset into table
ld a,(hl) ;a=SPT
inc b ;b=BPS (1-4)
ld hl,64 ;loop goes 1 extra
TKBPSL: add hl,hl ;compute bytes/sector
djnz TKBPSL
ld b,a ;b=SPT for return
ld de,IDRCST ;track ID required
;fall into PRTRYS
;===Subroutine=========================
;Print retries required
;to read track ID
;On entry:
; c=number of remaining tries to read
; de=Retry message
;Trashes a,de
;======================================
PRTRYS: ld a,(RTRIES) ;start value
sub c ;a=# of retries
ret Z ;no print if no retries
push hl
push af ;save retry count
call CRPRNT ;retry message
ld de,REQST ;required
call PRINTF
pop af ;calculated retries
push bc
call PDEC8
pop bc
pop hl
ld de,RTRYST ;retries
jp PRINTF
EJECT
;===Subroutine===========================
;Set up for read or write sector
;On entry:
; hl points to next chr in command line
;On Exit:
; disk ID is in COMBUF
; correct density in SDCTRL
; a=sector number
; b=2nd cmd line param, if any
; hl=# of bytes to transfer
; WDDM=A0DDM iff 'D' followed sector #
;Abort if track mismatch
;Trashes bc,de
;========================================
RWSTUP: ld bc,99 ;default, max
call GETDEC ;get sector number
jp Z,CMDERR ;error if no value
push af ;save desired sector
;Check if DDM is to be written
call GETASC
jr Z,RWSNOD ;no deleted data mark
cp 'D' ;deleted data mark req?
RWCERR: jp NZ,CMDERR
ld a,A0DDM ;deleted data mark
RWSNOD: ld (WDDM),a
call TRAKID ;get BPS, set density
;this overwrites DSCTR
ld a,(COMBUF) ;Track # from ID
ld b,a
in a,DTRCK ;which track?
ld de,TKMMST ;track mismatch
cp b
jp NZ,PRMAIN ;abort if mismatch
pop af ;requested sector
out DSCTR,a ;set WD1793 sector
ret
EJECT
;===Subroutine=========================
;Prepare for read or write disk command
;Select drive, abort if none
;Turn on motor, wait for spin-up
;get and test drive ready, abort if not
;Set auto-wait bit
;flush disk buffer
;On Exit:
; c=DDATA
; drive is ready
; Auto-wait is set
;Trashes a
;======================================
PREPD: push de
ld de,SDCTRL
ld a,(de)
or DCMOTO ;motor on
ld (de),a
push bc
call SELECT ;select the drive
call STALL1 ;Spindle motor start
pop bc
ld de,CBSYST ;WD1793 busy
in a,DSTAT
and SBUSY
PDERR: jp NZ,PRMAIN ;quit if busy
ld de,NRDYST ;not ready
in a,DSTAT
and SNORDY
jr NZ,PDERR ;quit if not ready
ld c,DDATA ;C value for ret
in a,(c) ;flush WD1793
ld a,(SDCTRL)
or DCAUTO ;set up auto-wait
out DCTRL,a
pop de
ret
EJECT
;===Subroutine===========================
;Select the drive and Restore to track 0
;Use direct control for voicecoil drives,
;use WD1793 restore command otherwise.
;Trashes a,c,de,hl
;Leaves unit selected
;Rude exit if no unit selected
;========================================
RESTOR: ld a,(DTYPE) ;which kind of restore?
or a
jr NZ,SLORES ;0 for voicecoil mode
;Fast restore, using voice-coil drive's restore signal
call NULSEK ;so we can read track0
;also SELECTs
ld a,(SIDENO) ;side # is in this reg
or a,DRESTR ;hardware restorecmd
out ACTRL,a ;start restore pulse
;Direct restore does not use the WD1793 restore
;function, so clear the track register here.
xor a ;clear WD1793 track reg
out DTRCK,a
;wait for seek complete, timeout after 4.8 seconds.
;Make sure we see track 0 when the seek is complete.
ld hl,2162h ;l=1 mS timeout for
;..REST0 loop, and
;..h=4.8 Sec timeout
;..for REST1 loop
;First Wait for the "seek in progress" signal from
;drive (slow on the PerSci 299B). If already on
;track 0 then we will timeout here after 1 mS and
;fall through the REST1 loop.
REST0: in a,ASTAT ;(11)wait for restore
and DASSIP ;(7)1=seek in progress
jr NZ,REST1 ;(7)
dec l ;(4)
jr NZ,REST0 ;(12)
;41 cycles=10.25 uS/pass
;Then wait for the restore to complete, with abort
;opportunity
REST1: call CHKCON ;(2240)user aborting?
ld de,RTOST ;(10)timeout message
dec hl ;(6)
ld a,h ;(4)
or a,l ;(4)
jp Z,PRMAIN ;(10)
in a,ASTAT ;(11)wait for finish
and DASSIP ;(7)1=seek in progress
jr NZ,REST1 ;(11)
;2303 cycles/pass=575.75 uS per pass
ld a,(SIDENO) ;get side # again
or ACSSM ;set side to end
out ACTRL,a ;...the restore pulse
ld de,TRK0ST ;Track 0 not detected
in a,DSTAT ;test for track 0
and STRAK0 ;1=track 0 detected
call Z,CRPRNT ;message if no track 0
ret
;WD1793 Restore for non-voice-coil drive
SLORES: call SELECT ;select the drive
ld a,RSTRCM+RS15MS ;restore slowly
;Fall into DSKCMD
EJECT
;===Subroutine===========================
;Send command to WD1793 & wait 'till done
;Rude abort if user quits
;On Entry:
; a=command
; drive is selected
;On Exit:
; c=Status after command
;Trashes a
;========================================
DSKCMD: out DCMMD,a ;WD1793 command
;Fall into EOJ
;===Subroutine==================
;Wait 'till WD1793 command done
;Rude abort if user quits
;On Entry:
; a=command
; drive is selected
;On Exit:
; c=Status after command
; interrupts enabled if INTIO
; Z flag set
;Trashes a
;===============================
EOJ:
if INTIO
ei ;allow I/O ints
endif
EOJLUP: call CHKCON ;user abort? (>56 uS)
in a,DSTAT ;wait for busy to end
ld c,a ;for ret
and SBUSY
jr NZ,eOJLUP
ret
EJECT
;===Subroutine============================
;Select the drive specified in SDCTRL, and
;test if any drive is actually selected.
;Print message and rudely exit if not.
;Trashes a,de
;=========================================
SELECT: ld a,(SDCTRL) ;select the drive
out DCTRL,a
and 0Fh ;any drive selected?
ld de,CANTST ;no drive selected
jp Z,PRMAIN ;rude abort
ret
;===Subroutine==============================
;Rewrite DCTL from SDCTRL and SHDLD
;On Exit:
; DCTRL=SDCTRL if heads loaded
; DCTRL=SDCTRL AND DCSMSK if heads unloaded
;Trashes a,c
;===========================================
REDCTL: ld a,(SDCTRL)
;Fall into DODCTL
;===Subroutine======================================
;Write to DCTl, taking into account head-load state,
;since heads are unloaded by deselecting the unit(s)
;On Entry:
; a=new DCTRL value
; SHDLD is valid
;On Exit:
; SDCTRL=a
; DCTRL=SDCTRL if heads loaded
; DCTRL=SDCTRL AND DCSMSK if heads unloaded
;Trashes a,c
;===================================================
DODCTL: ld (SDCTRL),a ;save state with unit
ld c,a
ld a,(SHDLD) ;heads loaded?
or a ;load or unload?
ld a,c
jr NZ,DOD1 ;load
and DCSMSK ;deselect to unload
DOD1: out DCTRL,a ;load/unload
ret
EJECT
;===Subroutine==================
;Read sector with retries
;On Entry:
; de=destination address
; hl=byte count
; RETRIES=number of retries
;On Exit:
; b=number of retries remaining
; c=final status
; Z set if success
;===============================
READS: ld a,(RTRIES)
ld b,a ;B counts tries
RDRTRY: call PREPD ;autowait, etc.
push hl ;save byte count
push de ;save dest address
ld a,RDSECT ;WD1793 command
call READD ;read the sector
pop de ;dest address
pop hl ;Bytes/Sect
ld a,c ;Final status
and SLOSTD+SCRCER+SRNFER
ret Z ;success return
djnz RDRTRY ;retry if we can
or a ;clear Z for error
ret
;===Subroutine==========================
;Read hl bytes from disk to memory at de
;58 cycles per pass
;On Entry:
; a=Disk Command (read or read ID)
; de=target address
; hl=byte count
; Disk is already selected and setup
;On Exit:
; c=final status
;Trashes a,de,hl
;=======================================
READD:
if INTIO
di ;no ints during read
endif
out DCMMD,a ;WD1793 command
READDL: in a,DFLAG ;(11)autowait
in a,DDATA ;(11)disk data
ld (de),a ;(7)
inc de ;(6)
dec hl ;(6)
ld a,h ;(4)
or l ;(4)
jp NZ,READDL ;(10)
jr EOJ ;get final status
EJECT
;===Subroutine======================================
;Seek the current track, so that index, track 0,etc.
;can be read, and so the spindle motor stays on
;On Exit:
; c=status from command
;Trashes a,b,de
;===================================================
NULSEK: call SELECT ;select the drive
;abort if none selected
call STALL1 ;spindle motor start
in a,DTRCK
out DDATA,a
ld a,DSEEK
jr DSKCMD ;ret from there
;===Subroutine=====
;Stall for 1 second
;Trashes a,b,de
;==================
STALL1: ld de,1193
ld b,0
STALUP: djnz STALUP ;(13X256=3328)
dec de ;(6)
ld a,d ;(4)
or e ;(4)
jr NZ,STALUP ;(12)
ret
EJECT
;===Subroutine=======================================
;Measure spindle rotational time by timing the index
;pulses. This assumes a 4MHz Z80 with no wait states,
;and 1 wait state to read DSTAT. Timeout when looking
;for the first edge, to insure we are getting edges.
;
;If the motor is off or the drive is not ready, don't
;try to measure spindle speed.
;On Entry:
; The drive is selected
; NULSEK has been called, so that INDEX is visible
;On Exit:
; Rotation time has been printed, unless the motor
; is off or the drive is not ready
; Timeout message printed if timeout
; If motor is supposed to be on, it's been nudged
; to restart motor-off timer
; hl=revolution time in 0.01 mS units
; hl=0 if motor off, drive not ready, or timeout
;Trashes a,c,de
;====================================================
IMEASR: ld hl,0
ld de,SDCTRL
ld a,(de) ;skip speed test
and DCMOTO ;...if motor is off
ret Z
ld a,(de) ;nudge motor to
out DCTRL,a ;...keep it spinning
in a,DSTAT ;skip speed test
and SNORDY ;...if drive not ready
ret NZ
ld de,MOTRST ;Spindle Motor:
call CRPRNT ;print message
ld c,SINDEX
ld de,TIMOST ;Timeout!
if INTIO
di ;no ints
endif
;Wait for a rising edge of INDEX
IMEAS1: inc hl ;(6)
ld a,h ;(4)
or l ;(4)
jr Z,IMTO ;(12/7)timeout?
in a,DSTAT ;(10+1W)wait low
and c ;(4)
jr NZ,IMEAS1 ;(12)
IMEAS2: inc hl ;(6)
ld a,h ;(4)
or l ;(4)
jr Z,IMTO ;(12/7)timeout?
in a,DSTAT ;(10+1W)wait for
and c ;(4);... rising edge
jr Z,IMEAS2 ;(12/7)
ld hl,-1 ;(10)
;Measure the time to the next rising edge of INDEX. The
;following 2 loops each take exactly 40 cycles (10 uS)
;per pass, with 1 wait state to read DSTAT. It takes
;exactly 40 cycles from the 1st edge (above) to the
;next read of DSTAT, and 40 cycles between DSTAT reads
;between the 2 loops below.
IMEAS3: inc hl ;(6) bump timer
in a,DSTAT ;(10+1W)wait low
ld d,0 ;(7) stall
and c ;(4) (clears C too)
jr NZ,IMEAS3 ;(12/7)
ret C ;(5) stall (C never set)
IMEAS4: inc hl ;(6) bump timer
in a,DSTAT ;(10+1W)wait for edge
ld d,0 ;(7) stall
and c ;(4)
jr Z,IMEAS4 ;(12/7)
if INTIO
ei ;allow I/O ints
endif
;hl has the time in 0.01 mS units
ld c,0 ;msb is 0
push hl ;save result for ret
call PRNTMS ;print hl in mS
pop hl
ld de,REVST ;mS/rev
IMTO:
if INTIO
ei ;ints ok
endif
jr PRINTF
EJECT
;===Subroutine=======================
;Print c,hl in mS
;On Entry:
; c,hl=value in 0.01 mS units
;On Exit:
; 'xxxx.xx mS' printed
;Trashes a,bc,de,hl
;====================================
;divide hl by 100 to get whole number mS portion
PRNTMS: ld a,c ;C has high digit
ld bc,-100
ld de,-1 ;initiaize quotent
PMSLP: inc de
add hl,bc ;subtract 100
jr C,PMSLP
add b ;b=-1 here
jr C,PMSLP ;dec, test high digit
ld bc,100 ;goes 1 too many times
add hl,bc ;hl now has remainder
push hl ;save remainder
ex de,hl ;whole # into hl
call PDEC16 ;print whole # in de
;Print decimal point, then fractional mS portion
ld a,'.'
call PRINTA
pop hl ;recover remainder
ld d,1 ;print leading 0's
call PRFRAC
ld de,MSST ;mS
jr PRINTF
EJECT
;===Subroutine===
;Print CR, LF
;Trashes a
;================
PCRLF: push de
ld de,CRLFST
call PRINTF
pop de
ret
;===Subroutine=========================
;Print CR,LF,$-terminated message at de
;Trashes a,de
;======================================
CRPRNT: call PCRLF
;Fall into PRINTF
;===Subroutine===================
;Print $-terminated message at de
;Trashes a,de
;================================
PRINTF: push bc
ld c,CPRINT
call CDOS
pop bc
ret
EJECT
;===Subroutine========
;Print head-load state
;Trashes a,de
;=====================
PHLOAD: ld de,HEADST ;Head
call CRPRNT
ld a,(SHDLD)
ld de,LDST ;loaded
or a ;0 maens not loaded
jr NZ,PH1
ld de,ULDST ;unloaded
PH1: jr PRINTF
;===Subroutine====
;Print motor state
;=================
PMOTOR: ld de,MOTST ;Motor
call CRPRNT
ld de,ONST ;on
ld a,(SDCTRL) ;test state
and DCMOTO ;just the motor bit
jr NZ,PM1
ld de,OFFST ;off
PM1: jr PRINTF
;===Subroutine===================
;Compute and report the data rate
;================================
PDRATE: ld de,DRATST ;data rate
call CRPRNT
ld hl,125 ;SD minidisk kbps
ld a,(SDCTRL) ;DCMAXI:4 DCDDEN:6
rlca ;DCMAXI:5 DCDDEN:7
rlca ;DCMAXI:6 DCDDEN:C
jr NC,BR0
add hl,hl ;double density
BR0: rlca ;DCMAXI:7
rlca ;DCMAXI:C
jr NC,BR1
add hl,hl ;8"disk
BR1: call PDEC16 ;hl=bit rate
ld de,KBPSST ;K-bits/sec
jr PRINTF
EJECT
;===Subroutine=========
;Print de as an address
;Trashes a,bc
;======================
PADDR: call PCRLF ;new line
ld a,d ;addr high byte
call PRHEX
ld a,e ;addr low byte
call PRHEX
ld a,':'
jr PRINTA
;===Subroutine==========
;Print a as 2 hex digits
;On Entry
; a=value to print
;Trashes a,bc
;=======================
PRHEX: ld b,a ;save digit
rrca ;hi nibble 1st
rrca
rrca
rrca
call PHXNIB
ld a,b ;recover lo nibble
;fall into PHXNIB
;---Local subroutine---
;Print hex nibble
;On Entry:
; Nibble in low 4 of a
;Trashes a,c
;----------------------
PHXNIB: and 0Fh ;get low
cp 0Ah ;A-F?
jr C,PHX1
add 'A'-'9'-1
PHX1: add '0'
;fall into PRINTA
;===Subroutine=========
;Print a on the console
;Trashes a,c
;======================
PRINTA: push de
ld e,a
ld c,CCONOT
call CDOS
pop de
ret
;===Subroutine===============
;Print a space on the console
;Trashes a,c
;============================
PSPACE: ld a,' '
jr PRINTA
EJECT
;===Subroutine========================================
;Get a line of input from the user. Echo normal chrs,
;but not CR. Immediate commands (which must be the
;first chr on the line) are executed.
;On Exit:
; 0-terminated line is in COMBUF
; hl=address of 1st non-blank chr in COMBUF
; Z set if line has 0 characters (besides blanks)
; Z and Cy set if immediate command was executed
; Abort to PRMAIN if ^C or ESC
;Trashes a,bc,de
;=====================================================
GETLIN: ld hl,COMBUF
push hl
ld b,0 ;chr count
;Backspace if possible
GLDEL: ld a,b ;can we back up?
or a
jr Z,GLOOP ;N: ignore
ld de,DELST ;BS,space,bS
GLBS: call PRINTF
dec b ;back up
dec hl
GLOOP: call HCONIN ;Get uppercase chr
;Keep head loaded
cp CR ;end of input?
jr Z,GLDON
cp BS ;any backspaces?
jr Z,GLDEL
cp DEL
jr Z,GLDEL
ld (hl),a ;save new chr
ld e,a ;echo character
ld c,CCONOT
call CDOS
ld a,b ;1st chr?
inc b ;count new chr
or a
ld a,(hl) ;recover chr
inc hl ;next slot in buffer
call Z,cKIMED ;y: immediate cmd?
;Z set if so
jr NZ,GLOOP ;n: look for another
scf ;indicate immediate
pop hl ;fix stack
ret
;TErminate complete input in COMBUF with a null
GLDON: ld (hl),0 ;install null term.
pop hl ;point to COMBUF
;Fall into SKIPSP
;===Subroutine=======================
;Skip over spaces in cmd line
;On Entry:
; hl=address in cmd line
;On Exit:
; a=next chr on line
; hl=address of first chr past space
; Z set and a=0 if no params found
;====================================
SKIPSP: dec hl ;account for init inc
SSLOOP: inc hl ;loop to skip spaces
ld a,(hl)
cp ' '
jr Z,SSLOOP
or a ;set or clr Z
ret
EJECT
;===Subroutine==========================
;Check for one of the immediate commands
;and execute it if so.
;On Entry:
; a=character
;On Exit if immediate command:
; a=0
; Z set
; Trashes c,de
;On Exit otherwise:
; all preserved
;=======================================
CKIMED: cp '+' ;step in?
jr Z,STEPIN
cp '-' ;step out?
jr Z,STEPOT
cp '>' ;nudge inward?
jr Z,NUDGIN
cp '<' ;nudge outward?
jr Z,NUDGOT
cp 'H' ;headload toggle?
ret NZ
;Fall into TOGHD
;===Subroutine===================
;Toggle the head-load state
;On Exit:
; a=0
; Z set
;Trashes c,de
;================================
TOGHD: ld a,(SHDLD) ;get prev state
xor 1 ;toggle it
ld (SHDLD),a ;remember state
push af ;Z means unloaded
call REDCTL ;write it to DCTL
;set/clear the WD1793 head load state using a null seek
in a,DTRCK ;seek track we're on
out DDATA,a
pop af ;Z set for unloaded
ld a,SEEKCM ;head unloaded
jr Z,TOGHD1 ;head loaded?
ld a,DSEEK ;null seek, head loaded
TOGHD1: out DCMMD,a ;issue WD1793 command
ld a,(VERBOS) ;verbose?
or a
call NZ,PHLOAD ;report headload if so
xor a ;clear Z
ret
EJECT
;===Subroutine===================
;Step in one track, if possible
;On Entry:
; VERBOS <> 0 enables printing
;On Exit:
; a=0
; Z set
;Trashes c,de
;================================
STEPIN: ld a,(MAXTRK)
ld c,a
in a,DTRCK ;where is the head now?
cp c ;max already?
ld c,DSTEPI ;step in (up track)
call C,STPSUB ;step cmd unless max
jr OPTRAK ;report track number,
;..ret with Z set
;===Subroutine===================
;Step out one track, if possible
;On Entry:
; VERBOS <> 0 enables printing
;On Exit:
; a=1 if verbose, 0 if not
; Z set
;Trashes c,de
;================================
STEPOT: in a,DTRCK ;where is the head now?
or a ;track 0 already?
ld c,DSTEPO ;step out (down track)
call NZ,STPSUB ;step cmd unless track0
;Fall into OPTRAK
;===Subroutine==============================
;Print the current track # only if VERBOS<>0
;On Entry:
; VERBOS <> 0 enables printing
;On Exit:
; Z set
; a=0
;Trashes de
;===========================================
OPTRAK: push hl
push bc
ld a,(VERBOS) ;verbose?
or a
call NZ,PTRACK ;print track number,
;..trashes regs
pop bc
pop hl
xor a ;for return
ret
;===Subroutine==========================
;Nudge the head inward and then back out
;On Exit:
; a=0
; Z set
; b and hl have been decremented
;Trashes c
;=======================================
NUDGIN: ld a,(MAXTRK)
ld c,a
in a,DTRCK ;where is the head now?
cp c ;max already?
jr NC,NIDONE ;y: then do nothing
ld c,DSTEPI ;step in (up track)
call STPSUB ;stepcmd
ld c,DSTEPO ;step in (down track)
call STPSUB ;stepcmd
NIDONE: jr OPTRAK ;recycle some code
;===Subroutine==========================
;Nudge the head outward and then back in
;On Exit:
; a=0
; Z set
; b and hl have been decremented
;Trashes c
;=======================================
NUDGOT: in a,DTRCK ;where is the head now?
or a ;track 0 already?
jr Z,NODONE ;y: then do nothing
ld c,DSTEPO ;step in (down track)
call STPSUB ;stepcmd
ld c,DSTEPI ;step in (up track)
call STPSUB ;stepcmd
NODONE: jr OPTRAK ;recycle some code
;---Local Subroutine----
;Get the step rate and
;issue the step command
;On Entry:
; c=step command
;Trashes a,c
;-----------------------
STPSUB: call SELECT ;select the drive
ld a,(STPRAT)
or c
jp DSKCMD ;issue command, ret
EJECT
;===Subroutine===================
;Ask user Y or N. return if Y,
;jump to PRMAIN for anything else
;On Entry:
; de=initial string
;Trashes a,c,de
;================================
ASKYN: call CRPRNT
call PRDRIV ;print drive letter
ld de,ASKRST ;ready (Y/N)?
call CRPRNT
ld c,CCONIE ;input with echo
call CDOS ;get chr with echo
ld de,aBRTST ;Abort
cp 'Y'
jp NZ,PRMAIN ;abort if not Y
ret
;===Subroutine===========================
;Get 1 ASCII chr from the cmd buffer
;On Entry:
; hl points to chr
;On Exit:
; a=chr
; Z set and a=0 if no chr
; hl points to first chr of next param
; Rude jump to GPERR if 2nd chr present
;Trashes c
;========================================
GETASC: ld a,(hl) ;get next chr
or a
ret Z ;no more
push af ;save chr and NZ
inc hl
call SKIPSP ;should be no more
jr NZ,GPERR ;too much crap
pop af ;recover chr and NZ
ret
;===Subroutine================================
;Get a 0 or 1 from the cmd buffer
;On Entry:
; hl points to first chr of a parameter
;On Exit:
; a=b=value
; Z set if value is 0
; hl points to first chr of next param
; Rude jump to CMDERR if:
; {no chr, bogus chr, value>1, another param}
;Trashes c
;=============================================
GET01: ld c,1
call GETDEC
jr Z,GPERR ;error if no chr
call SKIPSP ;should be no more
jr NZ,GPERR ;too much crap
ld a,b ;get, retest value
or a
ret ;with Z set
EJECT
;===Subroutine================================
;Get decimal parameter
;On Entry:
; b=default value
; c=max allowed value
; hl points to first chr of a parameter
;On Exit:
; a=b=value, default if no more chrs
; Z set if no parameters found
; Rude jump to CMDERR if bogus chr or value>c
;=============================================
GETDEC: call GETDD ;get 1st digit into a
ret Z ;return w/ default in b
ld b,a ;save 1st digit
call GETDD
jr Z,GPDON ;ret w/ 1-digit input
push af ;save new digit
ld a,b ;compute b=b*10
rlca ;*2
rlca ;*4
add b ;*5
rlca ;*10
ld b,a
pop af ;recover 1st digit
add b ;combine digits
ld b,a ;into B for ret
GPDON: ld a,(hl) ;any more digits?
or a ;end of line is ok
jr Z,GPDOK
cp ' ' ;separator is OK too
jr NZ,GPERR ;abort if any
GPDOK:
ld a,c ;max value from input
cp b ;legal value?
jr C,GPERR ;abort if not
or h ;clear Z
ld a,b ;result in a & b
ret
;---Local Subroutine-----------------------------
;Get & test a decimal digit
;On Exit:
; Z set if end of cmd line or no digit
; hl points to next cmd line chr unless it's ' '
; a=chr converted to binary, unless Z
;------------------------------------------------
GETDD: call GET1
ret Z
sub '0' ;de-ASCII
cp 10 ;carry means below 10
ret C
GPERR: jp CMDERR ;bogus: rude abort
EJECT
;===Subroutine=========================
;Get a multi-digit hex value
;On Entry:
; hl points to first hex digit
;On Exit:
; Z set and a=b=de=0 if no value found
; b=number of digits found
; de=value
; hl points to next input param
; Rude jump to CMDERR if bogus
;======================================
GETHEX: ld de,0 ;initial value
ld b,d ;b counts digits
call HEX2BN ;get 1st digit
ret Z ;Z: no chrs
GHLUP: inc b ;digit count
ex de,hl ;shift prev digits left
add hl,hl
add hl,hl
add hl,hl
add hl,hl
ex de,hl
add e ;combine new digit
ld e,a
call HEX2BN ;get another digit
jr NZ,GHLUP ;keep going until gone
call SKIPSP ;advance to next param
or 1 ;clear Z
ret
;---Local Subroutine-------------------
;Convert hex at (hl) to binary
;On Exit:
; a=chr, if any
; hl advanced if chr found
; Z set if no chrs
;Rude abort if bad hex
;--------------------------------------
HEX2BN: call GET1 ;get a character
ret Z ;none left?
sub '0' ;remove ASCII bias
cp 10
ret C ;done if 0-9
sub 9+('A'-'9') ;should be 0 to 5
cp 6 ;gap chr or too big?
jr NC,GPERR ;Y: bogus
add 0Ah ;make it A-F
ret
;===Subroutine======================
;Get 1 chr from command line
;On Exit:
; Z set if none left
; chr in a and advance hl otherwise
;===================================
GET1: ld a,(hl) ;any chrs?
or a
ret Z ;N:Z set
cp ' ' ;no digit?
ret Z
inc hl
ret
EJECT
;===Subroutine===================
;Set Force Step Mode bit in DTYPE
;All registers preserved
;================================
FRCST: push af
push hl
ld a,FRCSTP
ld hl,DTYPE
or (hl)
ld (hl),a
pop hl
pop af
ret
EJECT
;===Subroutine=================
;Check for user input
;TEst for ^C or ESC pressed
;Rude jump to PRMAIN if so
;On Exit:
; Z set if no chr waiting
; a=chr if not ^C or ESC
;Trashes c
;==============================
CHKCON: ld c,CCONST ;(7)console stat
call CDOS ;(2194)
or a ;(4)Z clear=chr waiting
ret Z ;(11)
;Fall into UCONIN
;===Subroutine===========================
;Get console chr and convert to uppercase
;Abort command if ^C or ESC
;On Exit:
; a=chr if not ^C or ESC
; Z cleared
;Trashes c
;========================================
UCONIN: ld c,CCONIN ;console input
call CDOS ;wait for console chr
sub 'a' ;convert lowercase
cp 'z'+1
jr NC,UCIN1
sub 'a'-'A' ;..to uppercase
UCIN1: add 'a'
cp CTRLC ;Aborting command?
jr Z,UCQUIT
cp ESC ;ESC aborts too
ret NZ
UCQUIT: ld de,CRLFST
jp PRMAIN
;===Subroutine========================
;Get console chr, convert to uppercase
;Keep head loaded while we wait
;Abort command if ^C or ESC
;On Exit:
; a=chr if not ^C or ESC
;=====================================
HCONIN: ld a,(SHDLD) ;current head-load state
or a ;head loaded?
jr Z,UCONIN ;n: just wait for the chr
;Issue null seek commands while we wait for the user
;to restart the head-load timer, to keep the head
;loaded
HCILUP: call CHKCON ;user input?
ret NZ ;y: return with chr
in a,DSTAT ;drive busy?
and SBUSY
jr NZ,hCILUP ;y: no need to kick it
in a,DTRCK ;seek track we're on
out DDATA,a ;(a null seek)
ld a,DSEEK ;null seek, head loaded
out DCMMD,a ;issue WD1793 command
jr HCILUP ;keep looking
SUBTTL Command TAble
;=============
;Command TAble
;=============
CMDTAB: db '?',0
dw FHELP ;Help
db 'BD'
dw FBDISP ;Display Buffer
db 'BE'
dw FEDIT ;Edit buffer
db 'BF'
dw FFILL ;Fill buffer
db 'EJ'
dw FEJECT ;Eject
db 'ID'
dw FTKID ;Track ID
db 'MO'
dw FMOTOR ;Motor
db 'QU'
dw WBOOT ;Quit
db 'RE'
dw FRESTR ;Restore
db 'SE'
dw FSET ;Settings
db 'SK'
dw FSEEK ;Seek
db 'ST'
dw FSTEP ;Step
db 'SR'
dw FREAD ;Read sector
db 'SW'
dw FWRITE ;Write sector
db 'SV'
dw FVERFY ;Verify sector
db 'VD'
dw FVDISP ;Display verify buffer
db 'WP'
dw FWPAT ;Write pattern
db 'A='
dw FSETA ;Auto mode
db 'D='
dw FSETD ;Density
db 'M='
dw FSETM ;Max track
db 'N='
dw FSETN ;Read Retries
db 'R='
dw FSETR ;Step rate
db 'S='
dw FSETS ;Side
db 'T='
dw FSETT ;Drive type
db 'U='
dw FSETU ;Unit
db 'V='
dw FSETV ;Verbose mode
db 0 ;TAble end
EJECT
;=================================================
;Sector per Track TAbles
;Each line is 5 bytes long, with SPT for single-
;density {--,128,256,512,1024} and double density
;{128,256,512,1024,--} bytes/sector. Note that the
;lines overlap. 0 means unsupported configuration
;=================================================
SPTTAB: db 0,18,10,5 ;minidisks
db 0,26,16,8,0 ;8" disks
;==========================
;Step rate table
;Correspond to WD1793 rates
;==========================
STPTAB: db 3 ;00
db 6 ;01
db 10 ;10
db 15 ;11
EJECT
SUBTTL STRINGS
;====================
;$-terminated strings
;====================
BANNER: db '===================================',CR,LF
db '= FLEXER Floppy Drive Exerciser =',CR,LF
db '= For Cromemco Disk Controllers =',CR,LF
db '= Version '
db (VERSION AND 0F00h)/256+'0','.'
db (VERSION AND 0F0h)/16+'0',(VERSION AND 0Fh)+'0'
db ' by M. Eberhard =',CR,LF
db '===================================',CR,LF
db CR,LF
db 'Type ? for help' ;fall into CRLFST
CRLFST: db CR,LF,'$'
PROMPT: db '%$'
DELST: db BS,' ' ;fall into BSST
BSST: db BS,'$'
HUHST: db 'Huh?$'
EJST: db 'Ejected$'
FSETST: db CR,LF,'==Current Settings & State==$'
MAXTST: db 'Max ' ;fall into TRAKST
TRAKST: db 'Track: $'
UNITST: db 'Unit: $'
SIDEST: db 'Side: $'
MODEST: db 'Mode: $'
AUTOST: db 'Automatic$'
MANST: db 'Manual$'
VERBST: db 'Verbose Mode: $'
MOTST: db 'Motor $'
ONST: db 'on$'
OFFST: db 'off$'
HEADST: db 'Head $'
ULDST: db 'un' ;fall into LDST
LDST: db 'loaded$'
DRIVST: db 'Drive $'
NRDYST: db 'not ' ;fall into RDTST
RDYST: db 'ready$'
DISKST: db 'Diskette $'
UNPRST: db 'not ' ;fall into PROTST
PROTST: db 'write protected$'
MOTRST: db 'Spindle Motor: $'
MSST: db ' mS$'
REVST: db '/rev$'
CANTST: db 'Can',QUOTE,'t. ' ;fall into NONEST
NONEST: db 'No unit selected$'
SKTOST: db 'Seek ' ;fall into TIMOST
TIMOST: db 'Timeout!$'
SKTST: db 'Seek Time: $'
STRST: db 'Step Rate: $'
DTYPST: db 'Drive Type: $'
VCST: db 'Voicecoil $'
MAXIST: db '8"$'
MINIST: db 'Minidisk$'
TRK0ST: db 'Track 0 $'
NDETST: db 'not ' ;fall into DETST
DETST: db 'detected$'
SNGLST: db 'Single$'
DUBLST: db 'Double$'
DENSST: db ' density$'
ASKSST: db 'Write from buffer to unit $'
ASKWST: db 'Insert unprotected disk in unit $'
ASKRST: db 'Ready (Y/N)? $'
BADPST: db 'Pattern bytes may not be between F5 and FE$'
ABRTST: db 'Abort$'
WPATST: db CR,LF,'==Write Pattern to Disk Track==',CR,LF
db CR,LF,'WARNING: This destroys formatting and data!$'
db CR,LF
DONEST: db 'Done$'
PLENST: db 'Pattern length: $'
SCONST: db ' -Space to continue, ESC to Quit-$'
BPSST: db ' bytes/sector$'
TKIDST: db CR,LF,'==Track ID==$'
CBSYST: db 'Error: WD1793 busy$'
BYRDST: db ' bytes read into buffer',CR,LF
db 'Deleted data mark $'
PREST: db 'present$'
ADELST: db 'absent$'
RVERST: db 'Verify ' ;fall into RERRST
RERRST: db 'Read Error$'
VERRST: db 'Verify Error$'
WFLTST: db 'Write Fault$'
IDERST: db 'Error reading track ID$'
RNFST: db 'Sector not found$'
TKMMST: db 'Track mismatch$'
SDMMST: db 'Side mismatch$'
BYWRST: db ' bytes written to disk$'
BYVFYD: db ' bytes verified$'
UNSUST: db 'Unsupported' ;fall into SPTST
SPTST: db ' Sectors/Track$'
RETRST: db 'Max read retries: $'
REQST: db ' required $'
IDRCST: db 'Track ID$'
SRRCST: db 'Sector read$'
VRRCST: db 'Verify read-back$'
RTRYST: db ' retries$'
DRATST: db 'Data rate: $'
KBPSST: db 'K bits/second$'
RTOST: db 'Restore Timeout!$'
HLPMSG:
; 12345678901234567890123456789012345678901234567890123456789012345678901234567890
db ' ==FLEXER Commands=='
db CR,LF
db 'BD Display read/write buffer ? Print Help screen'
db CR,LF
db 'BE Edit read/write buffer A={0/1} Auto mode {Off/On}'
db CR,LF
db 'BF Fill read/write buffer D={S/D} {Single/double} density'
db CR,LF
db 'EJ Eject diskette M={1-99} Max track number'
db CR,LF
db 'ID Read current track ID N={0-99} Max read retries'
db CR,LF
db 'MO Toggle/measure spindle motor R={0-3} Step rate'
db CR,LF
db 'QU Quit to CDOS S={0/1} Disk side'
db CR,LF
db 'RE Restore to track 0 T={5/8/V} {5.25"/8"/Voicecoil} drive'
db CR,LF
db 'SE Display settings & status U={A-D} Unit'
db CR,LF
db 'SK [...] Fast voicecoil-seek V={0/1} Verbose mode {Off/On}'
db CR,LF
db 'SR Read sector into R/W buffer IMMEDIATE COMMANDS'
db CR,LF
db 'ST [...] Track-by-track seek H Toggle head load'
db CR,LF
db 'SV Verify sector = R/W buffer + Step in one track'
db CR,LF
db 'SW [D] Write R/W buffer to sector - Step out one track'
db CR,LF
db ' [D sets deleted-data mark] > Nudge head inward'
db CR,LF
db 'VD Display verify buffer < Nudge head outward'
db CR,LF
db 'WP [...] Write pattern to track'
db CR,LF
db 'A toggles automatic mode and V toggles verbose mode during SK and ST.'
db CR,LF
db 'SK or ST with no arguments will use the previous track list.'
db CR,LF
db 'Use space bar to toggle motor or go to next track, use ^C or ESC to quit.'
db CR,LF
db 'For BE command, enter Hex value to change, CR to skip, ^C or ESC to quit.'
db '$'
EJECT
SUBTTL RAM Variables
;=========================
;Initialized RAM variables
;=========================
VERBOS: db 1 ;01h for verbose display mode
AUTOSK: db 0 ;01h for automatic seek/step
;AUTOSK must follow VERBOS
DTYPE: db DT8 ;Drive Type
DTVC equ 0 ;Voicecoil (must be 0)
DT8 equ 1 ;8" disk
DTMD equ 2 ;minidisk
DTHDMD equ 3 ;high-density minidisk
FRCSTP equ 80H ;force step mode
SIDENO: db ACSID0 ;side number, for ACTRL
SHDLD: db 0 ;1 for head loaded
SDCTRL: db DCMAXI+DCMOTO ;Current DCTRL value
STPRAT: db DEFSTP ;Step rate
MAXTRK: db MAXTK8 ;max track number
WDDM: db 0 ;request write deleted data mark
RTRIES: db DRTRYS+1 ;Max allowed Read tries (retries+1)
;===================
;Track Patern Buffer
;===================
PATBUF: db 0,0 ;first 2 bytes initialized
ds CBSIZE/2-4
;===========
;Local stack
;===========
ds 16
STACK: ;top of stack
;==================================
;Track buffer, used for track write
;(Overwrites the sector buffer and
;the verify buffer.)
;==================================
TRKBUF:
;==================================
;Sector buffer for reads and writes
;==================================
SECBUF: ds MAXBPS ;biggest possible sector
;====================
;Sector verify buffer
;====================
VERBUF: ds MAXBPS ;biggest possible sector
RAMEND equ $
END