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