.COMMENT ; Floppy Disc Test for Z-80 CP/M Systems Dr. Dobb's: April 1981 Version 1.0 The purchaser may freely create any number of copies of this program on magnetic media as necessary to support his computer system(s). Further resale of this program is prohibited. COPYRIGHT (c) 1980 by Ray Duncan Laboratory Microsystems 4147 Beethoven Street Los Angeles. CA 90066 ; CPM EQU 5H ; References to.. WBOOT EQU 0H ; ..operating system ; References to.. CR EQU 0DH ; ..ASCII characters LF EQU 0AH FF EQU 0CH TAB EQU 09H ; ; Parameters for disk (supplied as single density, soft sect.) ; $DRVF EQU 1H ; First drive to allow ; ..testing (0=A, 1=B etc.) $DRVL EQU 3H ; Last drive to allow test $TRKF EQU 0H ; First track $TRKL EQU 76 ; Last track $SECF EQU 1 ; First sector $SECL EQU 26 ; Last sector $BPS EQU 128 ; Bytes per sector $BPT EQU $BPS*$SECL ; Bytes per sector $DIG EQU 2 ; Number of digits to accept ; ..in track and sector assign. ; ..(Should be set larger for ; .. devices having track/ ; .. sector numbers >99 ) ; $VER EQU 1 ; Program version $REV EQU 0 ; .. revision ; .Z80 ASEG ; ORG 100H START: JP DTST ; Enter from CPM ; ; Global varables for use by all routines ; PASS: DW 0 ; Current pass ERRORS: DW 0 ; Error count for pass ; ; The following variables are used by RDBUF and WRTBUF to ; address the disk, and display failing disk addresses. ; DRV: DB 0 ; Drive to test TRK: DW 0 ; Current track SEC: DW 0 ; Current sector BUFFER: DW 0 ; Current memory address IOLEN: DW 0 ; Bytes last transferred ; ; The following variables define the area to be tested on ; the selected drive. ; TRKF: DW 0 ; First track to test TRKL: DW 0 ; Last track to test SECF: DW 0 ; First sector to test SECL: DW 0 ; Last sector to test ; ; The following variables define the test mode ; BYPASS: DB 0 ; 0=do not bypass error item. ; 1=bypass error itemization, ; ..print total errors per pass. SKEW: DB 0 ; 0=no sector skew ; 1=use sector skew for speed LIST: DB 0 ; 0=print errors on terminal ; 1=print errors on list dev. LOCKIO: DB 0 ; 0=no lock ; 1=lock on read ; 2=lock on write RESTOR: DB 0 ; 0=do not restore original ; ..data, 1=restore original ; ..data on disk. LOCKPT: DB 0 ; 0=use variable test data ; ..pattern, 1=lock on user ; ..supplied data pattern. PATTRN: DB 0 ; Contains user supplied ; ..8 bit data pattern. PASSL: DW 0 ; Last pass to do on this test DIGITS: DB $DIG ; Max. number of digits to be ; ..accepted during hex. or dec. ; ..numeric input. XTRAN: DW SECTRB ; Address of sector XLATE table ; ; Disk test --- main control ; DTST: ; Entry from CPM LD DE,DTSTA ; Print program title LD C,9 CALL CPM LD HL,(CPM+1) LD DE,BUFFEND OR A ; Make sure enough user SBC HL,DE ; ..memory to execute test JR NC,DTST01 LD DE,DTSTS ; Not enough memory LD C,9 ; Print warning and exit CALL CPM JP WBOOT DTST01: ; Ckeck CPM version LD C,12 CALL CPM LD A,L ; Make sure 2.x AND 0F0H CP 20H JR Z,DTST02 LD DE,DTSTZ ; Not CPM 2.x, print LD C,9 ; ..error message and quit CALL CPM JP WBOOT DTST02: ; Initialize variables XOR A LD (BYPASS),A LD (SKEW),A LD (LIST),A LD (LOCKIO),A LD (RESTOR),A LD (LOCKPT),A LD (PASS),A LD (PASS+1),A LD (ERRORS),A LD (ERRORS+1),A ; Now set up test configuration LD DE,DTSTB CALL GETYN ; Itemize errors? CP 'y' JR Z,DTST03 ; Yes LD A,1 ; No LD (BYPASS),A JR DTST04 ; Skip query for output device ; ..since errors will not be listed DTST03: ; Audit errors on console LD DE,DTSTC ; ..or line printer? CALL GETL CP 'c' JR Z,DTST04 ; c=use console CP 'p' CALL NZ,QUERY JR NZ,DTST03 ; No match, try again LD A,1 ; p=use line printer LD (LIST),A DTST04: ; Lock on read or write? LD DE,DTSTD CALL GETL CP 'n' ; n=no locks JR Z,DTST06 CP 'r' ; r=lock on read JR NZ,DTST05 LD A,1 LD (LOCKIO),A JR DTST12 ; Bypass queries about restore ; ..mode and data pattern, since ; ..we are locked in read mode DTST05: CP 'w' ; w=lock on write CALL NZ,QUERY JR NZ,DTST04 ; No match, try again LD A,2 LD (LOCKIO),A JR DTST08 ; Bypass restore question ; ..since we are locked in ; ..write mode DTST06: LD DE,DTSTE ; Restore user data? CALL GETYN CP 'y' ; y=restore JR NZ,DTST08 LD A,1 ; n=do not restore LD (RESTOR),A DTST08: LD DE,DTSTF ; Lock on data pattern? CALL GETYN CP 'n' JR Z,DTST12 ; n=use variable pattern LD A,1 ; y=lock on pattern LD (LOCKPT),A ; ..supplied by operator LD DE,DTSTG ; Accept data pattern CALL GETH ; ..from keyboard LD (PATTRN),A DTST12: LD DE,DTSTH ; Select drive to be tested CALL GETL SUB 'a' ; Convert to logical number CP $DRVF ; Make sure its legal CALL C,QUERY JR C,DTST12 ; Too small, try again CP $DRVL+1 CALL NC,QUERY JR NC,DTST12 ; Too large, try again LD (DRV),A ; Save drive assignment ADD A,'A' ; Also format for output LD (DTSTI1),A LD DE,DTSTI ; Confirm selected drive? CALL GETYN CP 'n' JR Z,DTST12 ; Not confirmed, try again LD HL,$TRKF ; Initialize track limits LD (TRKF),HL LD HL,$TRKL LD (TRKL),HL DTST15: LD DE,DTSTJ ; Test all tracks? CALL GETYN CP 'y' ; y=use all of them JR Z,DTST20 ; n=user wants to specify DTST17: LD DE,DTSTK ; Enter first track to test CALL GETN LD (TRKF),HL ; Save it LD DE,DTSTL ; Enter last track to test CALL GETN LD (TRKL),HL ; Save it LD DE,(TRKF) ; Make sure first OR A ; ..track<=last track SBC HL,DE CALL C,QUERY ; Wrong, start again JR C,DTST17 DTST20: LD HL,$SECF ; Initialize sector limits LD (SECF),HL LD HL,$SECL LD (SECL),HL DTST22: LD DE,DTSTM ; Use all sectors of each track CALL GETYN CP 'y' ; y=use all sectors JR Z,DTST26 ; n=user wants to specify DTST24: LD DE,DTSTN ; Enter first sector to test CALL GETN LD (SECF),HL ; Save it LD DE,DTSTO ; Enter last sector to test CALL GETN LD (SECL),HL ; Save it LD DE,(SECF) ; Make sure first OR A ; ..sector<=last sector SBC HL,DE CALL C,QUERY JR C,DTST24 ; Error, start again DTST26: ; All variables set up now -- LD DE,DTSTP ; ..how many test passes CALL GETN ; ..should be made? LD (PASSL),HL ; Save # of passes LD DE,DTSTT ; Print advisory message LD C,9 ; ..as test begins CALL CPM LD DE,DTSTU ; Remind user whether he is LD A,(RESTOR) ; ..using restore mode OR A JR Z,DTST32 LD DE,DTSTV DTST32: LD C,9 CALL CPM DTST40: ; Begin a pass LD HL,(TRKF) LD (TRK),HL ; Initialize current track DTST42: ; Process next track LD C,6 ; Check for interruption LD E,0FFH CALL CPM ; ..from console OR A JP NZ,DTST94 ; Break detected, quit LD A,(RESTOR) OR A ; Is this restore mode? JR Z,DTST45 ; No jump LD HL,BUFF3 ; Yes, save current disk LD DE,MERR1 ; ..contents CALL RDBUF DTST45: LD A,(LOCKIO) CP 1 ; Is this lock on read? JR Z,DTST47 ; Yes jump LD HL,BUFF1 ; Set up test pattern LD DE,$BPT CALL BUFPAT LD HL,BUFF1 ; Write test pattern LD DE,MERR2 CALL WTBUF DTST47: LD A,(LOCKIO) CP 2 ; Is this lock on write? JR Z,DTST70 ; Yes, jump LD HL,BUFF2 ; Read back test pattern ; (or just read existing data ; ..if locked on read) LD DE,MERR3 CALL RDBUF DTST50: LD A,(LOCKIO) OR A ; Is this lock on read or write JR NZ,DTST70 ; Yes, jump ; No, compare test data LD HL,BUFF1 ; ..written, to data read LD DE,BUFF2 ; ..back from disk. If LD BC,MERR4 ; ..difference found, CALL BUFCMP ; ..print error message DTST70: LD A,(RESTOR) OR A ; Using restore mode? JR Z,DTST80 ; No, jump ; Yes, write back user's data LD HL,BUFF3 LD DE,MERR6 CALL WTBUF LD HL,BUFF1 ; Verify that LD DE,MERR7 ; ..it was rewritten OK CALL RDBUF LD HL,BUFF1 LD DE,BUFF3 LD BC,MERR5 ; Check restored data CALL BUFCMP ; If difference found, print ; ..'data cannot be restored' DTST80: LD DE,(TRK) ; Advance current track INC DE LD (TRK),DE LD HL,(TRKL) OR A ; Done with all tracks? SBC HL,DE JP NC,DTST42 ; No, process another DTST90: ; End of pass LD BC,(PASS) INC BC ; Count passes LD (PASS),BC LD HL,DTSTR1 CALL CONV ; Convert pass # LD BC,(ERRORS) LD HL,DTSTR2 CALL CONV ; Convert error count LD DE,DTSTR ; Print pass and errors LD C,9 ; ..on console CALL CPM LD A,(LIST) ; Also using printer? OR A JR Z,DTST92 ; No, jump ; Yes, also send pass and error ; ..count to printer LD HL,DTSTR CALL PERR9 DTST92: ; Reset error count XOR A LD (ERRORS),A LD (ERRORS+1),A LD HL,(PASS) LD DE,(PASSL) OR A ; Are enough passes done? SBC HL,DE JP C,DTST40 ; Not yet, loop done with all DTST94: ; ..passes LD DE,DTSTW ; Ask whether to exit CALL GETL ; ..or to continue test CP 'c' ; c=continue JP Z,DTST CP 'e' ; e=exit JR NZ,DTST94 ; If no match, try again LD DE,DTSTX ; Print goodbye LD C,9 CALL CPM ; ..and return control JP WBOOT ; ..to CP/M ; ; Routines to read and write up to one track ; RDBUF: ; Read current track from ; ..secf to secl ; Call hl=buffer base addr ; .. de=error msg addr LD (RDBUFA),DE ; Save message address LD (BUFFER),HL ; Save buffer address LD HL,0 ; Initialize transfer byte LD (IOLEN),HL ; ..count CALL SELDSK ; Select disk LD HL,(SECF) LD (SEC),HL ; Initialize current sector RDBUF1: CALL SETIO ; Setup track, sector, memory CALL READ ; Now request transfer OR A ; Was I/O successful? JR Z,RDBUF2 ; No error, jump LD DE,(RDBUFA) CALL PERR ; I/O error, audit it RDBUF2: CALL RWADV ; Advance sector address JR NC,RDBUF1 ; Not done, read another RET ; Back to caller ; RDBUFA: DW 0 ; Address of error message ; WTBUF: ; Write current track ; ..from secf to secl ; Call de=error msg addr ; .. hl=buffer base addr LD (WTBUFA),DE ; Save message address LD (BUFFER),HL ; Save memory addr LD HL,0 ; Initialize transfer LD (IOLEN),HL ; ..byte count CALL SELDSK ; Select disk drive LD HL,(SECF) LD (SEC),HL ; Initialize current sector WTBUF1: CALL SETIO ; Set track, sector, memory CALL WRITE ; Request disk write OR A ; Any I/O errors? JR Z,WTBUF2 ; No, jump LD DE,(WTBUFA) CALL PERR ; Error, audit it WTBUF2: CALL RWADV ; Advance sector address JR NC,WTBUF1 ; Not done, write another RET ; Back to caller ; WTBUFA EQU RDBUFA ; Save address of error msg ; RWADV: ; Advance sector and memory addr LD DE,$BPS ; de<--bytes per sector LD HL,(BUFFER) ADD HL,DE ; Update buffer address LD (BUFFER),HL LD HL,(IOLEN) ADD HL,DE ; Count bytes transferred LD (IOLEN),HL LD DE,(SEC) ; Advance current sector INC DE LD (SEC),DE LD HL,(SECL) OR A ; Done with all sectors? SBC HL,DE ; Exit with carry set if done RET ; ; Set up buffer with test pattern ; BUFPAT: ; Call hl=buffer base address ; .. de=byte length to set up LD A,(LOCKPT) OR A ; Are we locked on user specified ; ..data pattern? JR NZ,BUFPA2 ; Yes, jump BUFPA1: LD A,R ; Read refresh register XOR H ADD A,L ; Make data a function of memory ; ..address LD (HL),A ; ..and store it INC HL ; Advance buffer address DEC DE ; Count bytes stored LD A,D ; Done yet? OR E JR NZ,BUFPA1 ; No, loop RET BUFPA2: LD A,(PATTRN) ; User specified parrern LD (HL),A ; Store one byte INC HL ; Advance buffer pointer DEC DE ; Count bytes stored LD A,D ; Done yet? OR E JR NZ,BUFPA2 ; Not done, loop RET ; Exit ; ;Compare specified buffer and print error message if difference found ; BUFCMP: ; Call bc=error msg addr ; .. de=1st buffer addr ; .. hl=2nd buffer addr LD (BUFCMA),BC ; Save message addr LD (BUFCMB),HL ; Save base of buffer LD BC,(IOLEN) ; Length to compare BUFCM1: LD A,(DE) ; Fetch byte from 1st buffer CP (HL) ; Compare it to 2nd buffer JR NZ,BUFCM3 ; Difference found, jump BUFCM2: INC HL ; Advance buffer addresses INC DE DEC BC ; Count bytes LD A,B ; Done yet? OR C JR NZ,BUFCM1 ; No, loop RET ; Back to caller BUFCM3: ; Difference found, print ; ..error audit trail PUSH BC ; First save registers PUSH DE PUSH HL LD DE,(BUFCMB) OR A SBC HL,DE ; Find a buffer offset PUSH HL ; Now divide by bytes per POP BC ; ..sector to find relative LD DE,$BPS ; ..sector number CALL DIV LD HL,(SECF) ADD HL,BC ; Add relative sector to first ; ..sector to find actual ; ..address for use by PERR LD (SEC),HL LD DE,(BUFCMA) CALL PERR ; Now audit error POP HL ; Restore registers POP DE POP BC BUFCM4: ; Advance memory address out of ; ..this sector where an error ; ..was found INC HL ; Bump buffer addresses INC DE DEC BC ; Done with all data area? LD A,B OR C RET Z ; Yes, exit compare routine LD A,L ; Check if on new sector AND $BPS-1 ; ..boundary JR Z,BUFCM1 ; Found it, go compare more data JR BUFCM4 ; Keep adv until sector boundary ; BUFCMA: DW 0 ; Address of error message BUFCMB: DW 0 ; Base buffer addresses ; ; Error printing routine, prints pass, drive, track, sector ; and message specified by caller on the console device. ; PERR: ; Call de=error message address LD A,(BYPASS) OR A ; Is error itemization bypass set JR NZ,PERR2 ; Yes, skip printing and go count LD (PERRA),DE ; Save message address LD BC,(PASS) INC BC LD HL,PERRC ; Convert current pass CALL CONV LD A,(DRV) ; Form drive name ADD A,'A' LD (PERRD),A LD BC,(TRK) ; Convert current track LD HL,PERRE CALL CONV LD BC,(SEC) ; Convert current sector LD A,(SKEW) ; Is skew in effect? OR A JR Z,PERR0 ; No CALL SECTRAN ; Yes, translate sector PERR0: LD HL,PERRF CALL CONV LD A,(LIST) ; Should output be on OR A ; ..console or printer? JR NZ,PERR3 ; Jump, use printer, fall thru LD HL,(ERRORS) ; ..use console LD A,H ; Is this the first error? OR L JR NZ,PERR1 ; No, jump LD DE,DTSTQ ; Print title for errors LD C,9 CALL CPM PERR1: LD DE,PERRB ; Print disk address LD C,9 CALL CPM LD DE,(PERRA) ; Print error type LD C,9 CALL CPM PERR2: LD HL,(ERRORS) ; Count errors INC HL LD (ERRORS),HL RET ; Back to caller PERR3: ; Errors to printer LD HL,(ERRORS) LD A,H ; Is this 1st error to be OR L ; ..printed this pass? JR NZ,PERR4 ; No, jump LD HL,DTSTQ ; Yes, print title CALL PERR9 PERR4: LD HL,PERRB ; Print disk address CALL PERR9 LD HL,(PERRA) CALL PERR9 ; Print error type JR PERR2 ; Go count errors PERR9: ; Send a string terminated by ; ..'$' to list device LD A,(HL) ; Fetch next character CP '$' ; Is it terminator? RET Z ; Yes, exit PUSH HL ; Save string address LD E,A ; Send this character LD C,5 CALL CPM POP HL ; Restore string address INC HL ; ..and increment it JR PERR9 ; Check next character ; PERRA: DW 0 ; Addrs of msg describing error PERRB: DB CR,LF PERRC: DB 'nnnn ' ; Pass # PERRD: DB 'n ' ; Drive PERRE: DB 'nnnn ' ; Track PERRF: DB 'nnnn $' ; Sector ; ; Disk interface to CP/M BIOS ; SELDSK: LD A,(DRV) ; Select disk drive LD C,A LD DE,24 JPBIOS: ; This routine links to the LD HL,(WBOOT+1) ; ..desired routine through ADD HL,DE ; ..the standard CP/M JP (HL) ; ..BIOS jump table SETTRK: LD BC,(TRK) ; Select track LD DE,27 JR JPBIOS SETSEC: LD BC,(SEC) ; Select sector LD DE,30 LD A,(SKEW) ; Use sector skew? OR A JR Z,JPBIOS ; No CALL SECTRAN ; Translate sector addr JR JPBIOS SETDMA: LD BC,(BUFFER) ; Set memory addr LD DE,33 JR JPBIOS SETIO: CALL SETTRK ; Set up track, sector, CALL SETSEC ; ..and memory address CALL SETDMA ; ..for subsequent read RET ; ..or write READ: LD DE,36 ; Read one disk sector JR JPBIOS WRITE: LD DE,39 ; Write one disk sector JR JPBIOS SECTRAN: ; Translate logical to physical ; Call bc=logical sector ; .. hl=physical sector PUSH HL LD HL,SECTRB-1 ADD HL,BC LD C,(HL) POP HL RET ; SECTRB: DB 1,7,13,19,25 ; Table built DB 5,11,17,23 ; ..with skew DB 3,9,15,21 ; ..factor = 6 DB 2,8,14,20,26 ; 128 byte sector DB 6,12,18,24 DB 4,10,16,22 ; ; Messages for test initialization and error printing ; DTSTA: DB CR,LF,LF DB 'Z-80 Floppy Disk ' DB 'Test version ' DB $VER+'0','.' DB $REV+'0',CR,LF DB '(c) 1980 Laboratory ' DB 'Microsystems',CR,LF,'$' DTSTB: DB CR,LF,'Itemize ' DB 'errors? $' DTSTC: DB CR,LF,'Use ' DB 'console or printer' DB '? (C/P) $' DTSTD: DB CR,LF,'Lock on read ' DB 'or write? (N/R/W) $' DTSTE: DB CR,LF,'Restore ' DB 'original data? $' DTSTF: DB CR,LF,'Lock on ' DB 'data pattern? $' DTSTG: DB CR,LF,'Enter data ' DB 'pattern, hex 00-FF$' DTSTH: DB CR,LF,'Drive ' DB 'to be tested ' DB '(',$DRVF+'A','-' DB $DRVL+'A',') $' DTSTI: DB CR,LF,'Confirm: test drive ' DTSTI1: DB 'X ? $' DTSTJ: DB CR,LF,'Test all ' DB 'tracks? $' DTSTK: DB CR,LF,'First ' DB 'track to test $' DTSTL: DB CR,LF,'Last ' DB 'track to test $' DTSTM: DB CR,LF,'Test all ' DB 'sectors? $' DTSTN: DB CR,LF,'First ' DB 'sector to test $' DTSTO: DB CR,LF,'Last ' DB 'sector to test $' DTSTP: DB CR,LF,'How many ' DB 'test passes? $' DTSTQ: DB CR,LF,LF,'Pass ' DB 'Drive Track ' DB 'Sector Error-type' DB CR,LF,'$' DTSTR: DB CR,LF,LF,'Pass ' DTSTR1: DB 'nnnn complete, ' DTSTR2: DB 'nnnn errors.' DB CR,LF,'$' DTSTS: DB CR,LF,'Not enough ' DB 'memory to execute.' DB CR,LF,'$' DTSTT: DB CR,LF,LF,'Beginning ' DB 'disk test - push ' DB 'any key to abort ' DB 'program. ',CR,LF,'$' DTSTU: DB 'WARNING: user data ' DB 'will not be ' DB 'restored. ',CR,LF,'$' DTSTV: DB 'User data will be ' DB 'restored. ',CR,LF,'$' DTSTW: DB CR,LF,'Continue or ' DB 'exit test? (C/E)$' DTSTX: DB CR,LF,LF DB 'Goodbye. ',CR,LF,'$' DTSTY: DB CR,LF,'Use sector ' DB 'skew? $' DTSTZ: DB CR,LF,'Need CP/M 2.x ' DB 'to execute. ',CR,LF,'$' MERR1: DB 'read error - original data$' MERR2: DB 'write error - test pattern$' MERR3: DB 'read error - test pattern$' MERR4: DB 'compare error - test pattern$' MERR5: DB 'original data cannot ' DB 'be restored$' MERR6: DB 'write error - restore phase$' MERR7: DB 'read error - restore phase$' ; ; Utility and console input routines ; GETYN: ; Get y or n response from operator ; Call de= address of cue ; .. a=y or n PUSH DE ; Save cue address LD C,9 ; Print cue message CALL CPM LD DE,GETYNA ; Print possible answers LD C,9 CALL CPM CALL GETCHAR ; Get a character from console OR 20H ; Fold to lowercase POP DE ; Restore cue address ; ..in case needed again CP 'y' ; Make sure response is OK RET Z ; Exit if y CP 'n' RET Z ; Exit if n PUSH DE CALL QUERY ; Print '?' if not POP DE ; ..y or n, try again JR GETYN ; GETYNA: DB '(Y/N) ',TAB,'> $' ; GETL: ; Get any response from operator ; Call de=cue address ; .. a=ASCII character LD C,9 ; Print cue message CALL CPM LD DE,GETLA ; Tab and print LD C,9 ; ..cue mark CALL CPM CALL GETCHAR ; Read console OR 20H ; Fold lowercase RET ; GETLA: DB TAB,'> $' ; GETN: ; Get a decimal # from the console ; Call de=cue address ; ..return hl=number PUSH DE ; Save cue message address LD C,9 CALL CPM ; Print cue message LD DE,GETNA ; Print tab and cue mark LD C,9 CALL CPM LD HL,0 ; Initialize forming answer LD A,(DIGITS) LD B,A ; Total chars allowed to be input GETN1: PUSH HL ; Save answer PUSH BC ; Save char count CALL GETCHAR ; Read console POP BC ; Restore char count POP HL ; Restore forming answer CP CR ; Is this return? JR Z,GETN9 ; Yes, exit with answer CP '0' ; Is this legal char.? JR C,GETN3 ; No, jump CP '9'+1 ; Is this legal char.? JR NC,GETN3 ; No, jump AND 0FH ; Isolate bottom 4 bits PUSH HL ; Previous data * 10 POP DE ADD HL,HL ; *2 ADD HL,HL ; *4 ADD HL,DE ; *5 ADD HL,HL ; *10 LD E,A ; Now add in this digit LD D,0 ADD HL,DE DJNZ GETN1 ; Count characters accepted JR GETN9 ; Enough accepted, exit GETN3: ; Illegal character detected CALL QUERY ; Print '?' and POP DE ; ..restart input JR GETN GETN9: ; Input complete, clean POP DE ; ..stack and exit with RET ; ..answer in (hl) ; GETNA: DB TAB,'> $' GETNB: DB '?$' ; GETH: ; Get $dig hex digits from kbd ; Call de=cue address ; Return a=lower 8 bits of # ; .. hl=entire 16 bit # PUSH DE ; Save cue address LD C,9 CALL CPM ; Print cue message LD DE,GETHA ; Print tab and cue mark LD C,9 CALL CPM LD HL,0 ; Initialize forming answer LD A,(DIGITS) LD B,A ; Max digits to accept GETH1: PUSH BC ; Save registers PUSH HL CALL GETCHAR ; Read console POP HL POP BC ; Restore registers CP CR ; If exit JR Z,GETH25 CP '0' ; Make sure its legal JR C,GETH3 ; No, jump CP '9'+1 ; If alpha fold to JR C,GETH15 ; ..lowercase OR 20H GETH15: CP 'f'+1 ; Make sure its legal JR NC,GETH3 ; No, jump CP 'a' ; Check if alpha JR C,GETH2 ; Jump if 0-9 ADD A,9 ; Add correction GETH2: AND 0FH ADD HL,HL ; Previous data *16 ADD HL,HL ; (Left shift 4 bits) ADD HL,HL ADD HL,HL ADD A,L ; Add this char to it LD L,A ; ..forming result DJNZ GETH1 ; Keep reading console GETH25: POP DE ; Clean up stack LD A,L ; Put lower 8 bits of answer ; ..in 'A'. (incase exit by ) RET GETH3: CALL QUERY ; Print '?' POP DE ; ..then restart input JR GETH ; GETHA: DB TAB,'> $' ; QUERY: PUSH AF ; Save flags LD C,9 ; Print '?' LD DE,QUERYA CALL CPM POP AF ; Restore flags RET ; QUERYA: DB ' ?$' ; GETCHAR: ; Get 1 char from console via ; ..raw input mode. Do not echo ; .. LD E,0FFH LD C,6 CALL CPM ; Read console OR A ; Anything there? JR Z,GETCHAR ; No, try again CP CR ; Is it ? RET Z ; Yes PUSH AF ; No, echo it LD E,A LD C,6 CALL CPM POP AF ; Restore 'A' and exit RET CONV: ; Convert binary to decimal ASCII ; Call bc=binary, in range 0000-9999 ; .. hl=first byte addr to store LD DE,1000 CALL DIV CALL CONV9 ; Thousands digit LD DE,100 CALL DIV CALL CONV9 ; Hundreds digit LD DE,10 CALL DIV CALL CONV9 ; Tens digit CALL CONV9 ; Units RET ; Back to caller CONV9: LD A,C ; Turn quotient into ADD A,'0' ; ..ASCII char and LD (HL),A ; ..store it INC HL ; Bump output pointer PUSH DE ; bc<--remainder POP BC RET DIV: ; Single precision divide ; Call bc=numerator ; .. de=divisor ; Return bc=quotient ; .. de=remainder PUSH HL LD HL,0 OR A SBC HL,DE EX DE,HL LD HL,0 LD A,17 DIV0: PUSH HL ADD HL,DE JR NC,DIV1 EX (SP),HL DIV1: POP HL PUSH AF RL C RL B RL L RL H POP AF DEC A JR NZ,DIV0 OR A RR H RR L EX DE,HL POP HL RET ; BUFF1 EQU 1000H ; Disk buffers BUFF2 EQU $BPT*2+BUFF1 BUFF3 EQU $BPT*2+BUFF2 BUFFEND EQU $BPT*2+BUFF3 ; END 100H