>'BOOK1P2 - uSEFUL sUBROUTINES
 
    a USEFUL SUBROUTINE IS WHERE A BLOCK OF DATA NEEDS TO BE MOVED FROM ONE LOCATION TO ANOTHER.  tHIS IS ACCOMPLISHED VERY FAST USING BLOCK MOVE INSTRUCTION (ldir, ETC.), BUT IF THE DATA IS OVERLAPPING, THE TRANSFER DIRECTION BECOMES CRITICAL.  iF DATA IS MOVED IN THE WRONG DIRECTION IN THIS CASE, IT MAY DESTROY SOUCE DATA BEFORE IT CAN BE RELOCATED.  tHE FOLLOWING SUBROUTINE WILL SAFELY MOVE ANY SIZE BLOCK OF DATA FROM ANY LOCATION TO ANY OTHER SAFELY, EVEN IF THE BLOCKS OVERLAP.  wE WILL ASSUME THAT THE hl REGISTERS POINT TO THE START OF THE SOURCE BLOCK, AND THAT THE de REGISTERS POINT TO THE START OF THE BLOCK WHERE IT IS TO BE MOVED TO.  tHE bc REGISTERS CONTAIN THE LENGTH OF THE BLOCK THAT IS TO BE MOVED.
 
@move    push      hl         ;SAVE FROM SUBTRACT
         or        a          ;CLEAR CARRY FLAG FOR SUBTRACT
         sbc       hl,de      ;COMPARE THE TWO ADDRESSES
         pop       hl         ;RESTORE POINTER
         jr        c,movbak   ;MUST MOVE IN REVERSE ORDER
         ldir                 ;ELSE CAN MOVE THIS WAY
         ret                  ;DATA MOVED
movbak   add       hl,bc      ;POINT TO DATA END
         dec       hl         ;END -1
         ex        de,hl      ;NOW ADD TO de ALSO
         add       hl,bc      ;POINT TO DEST END
         dec       hl         ;DEST END -1
         ex        de,hl      ;RESTORE SOURCE, DEST POINTER
         lddr                 ;MOVE DATA BACKWARDS
         ret                  ;DATA MOVED
 
    sIMILAR TO THE @move ROUTINE, SOMETIMES IT IS NECESSARY TO EXCHANGE TWO BLOCKS OF DATA IN MEMORY.  tHE FOLLOWING SUBROUTINE WILL DO THIS.  tHE REGISTERS WILL BE THE SAME AS IN THE @move SUBROUTINE.  iN THIS CASE HOWEVER, THE BLOCKS MAY NOT BE OVERLAPPING OR THE DATA MAY BE INACCURATE.
 
@exchg   push      hl         ;SAVE SOURCE STRING
exclp    ld        a,(hl)     ;FETCH SOURCE BYTE
         ex        af,af'     ;SAVE IT HERE
         ld        a,(de)     ;GET DEST BYTE
         ld        (hl),a     ;SAVE INTO SOURCE
         ex        af,af'     ;GET SOURCE BACK
         ld        (de),a     ;SAVE INTO DEST
         inc       hl         ;BUMP POINTERS
         inc       de
         dec       bc         ;DECREMENT COUNTER
         ld        a,b        ;SEE IF MORE TO DO
         or        c          ;ANY BITS ON?
         jr        nz,exclp   ;GO MORE
         ret                  ;DATA EXCHANGED
 
    aNOTHER SIMPLE DATA OPERATION CALL THAT WE WILL USE EXTENSIVELY WHEN CREATING FORMATTING TRACK IMAGES IN MEMORY.  tHIS IS SIMPLY A "FILL" ROUTINE THAT WILL REPEAT ANY SPECIFIED BYTE, ANY NUMBER OF TIMES, TO ANY MEMORY ADDRESS.  wE WILL ASSUME THAT hl POINTS TO THE START ADDRESS OF THE FILL, a CONTAINS THE FILL CHARACTER, AND b CONTAINS THE NUMBER OF BYTES TO BE FILLED.
 
@fill    ld        (hl),a     ;LOAD A BYTE
         inc       hl         ;BUMP THE POINTER
         djnz      @fill      ;DO FOR b REGISTER TIMES
         ret                  ;THAT'S ALL
 
    wHEN A FILESPEC IS ENTERED TO dos, IT MAY OPTIONALLY INCLUDE A PASSWORD OF UP TO EIGHT CHARACTERS.  tHIS PASSWORD IS THEN "HASHED" INTO A WORD (TWO BYTES) OF BINARY DATA.  tHIS SERVES TWO PURPOSES.  tHERE IS NO WAY FOR A PERSON TO EXAMINE THE DISKETTE VIA A "ZAPPER" AND SEE THE PASSWORDS IN ASCII.  iT ALSO ALLOWS THE SYSTEM TO "PACK" THE DATA INTO A MUCH SMALLER SPACE.  tHE FOLLOWING ROUTINE IS THE TECHNIQUE THAT IS USED TO "CODE" THE PASSWORD INTO THE HASHED WORD.  bECAUSE MANY PASSWORDS MAY CODE INTO THE SAME TWO BYTES, IT IS IMPOSSIBLE FOR A HASHED PASSWORD TO BE RE-CONVERTED BACK TO THE ORIGINAL STRING.  iT IS POSSIBLE, HOWEVER, TO FIND A STRING THAT WILL CODE INTO THE SAME TWO BYTES AS THE ORIGINAL PASSWORD.
    iN THIS FIRST SUBROUTINE, hl WILL BE POINTING TO A STRING THAT IS TO BE CODED.  tHE b REGISTER WILL CONTAIN THE LENGTH OF THE STRING.  oN RETURN, hl WILL CONTAIN THE PASSWORD, de IS UNCHANGED, AND bc ARE DESTROYED.  a PASSWORD IS CODED USING ALL 8 BYTES, AND THEREFORE THE STRING MUST BE LEFT JUSTIFIED AND PADDED WITH SPACES ON THE RIGHT.  tHIS SUBROUTINE WILL AUTOMATICALLY FILL THE STRING TO THE CORRECT LENGTH PROVIDED THE b REGISTER ACCURATELY REFLECTS THE SIGNIFICANT LENGTH OF THE INPUT.  iF A CALL WAS MADE TO @getstr, FOR EXAMPLE, TO FETCH THE INPUT STRING, IT WILL BE IN CORRECT FORMAT FOR A CALL TO @code.  iF THE USER HAS ALREADY PREPARED THE STRING CORRECTLY PADDED, A CALL MAY BE MADE TO code TO SAVE TIME, IN WHICH CASE THE b REGISTER NEED NOT BE SUPPLIED, BUT IS STILL USED.  tHIS TECHNIQUE WILL BE DEMONSTRATED NEXT.  nOTICE THAT THE mOD i AND iii trsdos BOTH HAVE SIMILAR CODE WITH THE SAME LENGTH, AND DIFFER BY ONLY TWO INSTRUCTIONS.  tHIS, OF COURSE, MAKES THE PASSWORDS INCOMPATABLE BETWEEN THE TWO SYSTEMS.
 
@code    ld        a,8        ;LENGTH NEEDED
         sub       b          ;LESS WHAT WE HAVE
         jr        z,code     ;ALREADY CORRECT
         ld        c,b        ;GET LENGTH
         ld        b,0        ;bc = LENGTH
         push      hl         ;SAVE START OF STRING
         add       hl,bc      ;hl=> END OF STRING
codefl   ld        (hl),20h   ;PUT A SPACE HERE
         inc       hl         ;BUMP POINTER
         dec       a          ;DECREMENT COUNTER
         jr        nz,codefl  ;FILL THE REST NEEDED
         pop       hl         ;GET START BACK
code     ld        bc,7       ;NEED TO START AT LAST CHAR
         add       hl,bc      ;hl => LAST CHARACTER
         push      de         ;SAVE, WE NEED TO USE IT
         ex        de,hl      ;de => LAST
         ld        hl,-1      ;STARTING PASSWORD (ffffh)
         ld        b,8        ;8 LOOPS
codelp   ld        a,(de)     ;GET A STRING BYTE
         push      de         ;SAVE POINTER
         call      @ucase     ;MAKE IT UPPER CASE
         ld        d,a        ;SAVE HERE
         ld        e,h        ;CODING SCHEME
         ld        a,l
         and       7
         rrca
         rrca
         rrca
         xor       l
         ld        l,a
         ld        h,0
         if        mod1
         add       hl,hl
         add       hl,hl
         endif
         if        mod3
         sbc       hl,de
         endif
         add       hl,hl
         add       hl,hl
         xor       h
         xor       d
         ld        d,a
         ld        a,l
         add       hl,hl
         xor       h
         xor       e
         ld        e,a
         ex        de,hl      ;hl HAS THE TEMPORARY WORD
         pop       de         ;GET STRING POINTER BACK
         dec       de         ;WORKING BACKWARDS THROUGH STRING
         djnz      codelp     ;DO ALL 8 CHARACTERS
         pop       de         ;RESTORE TO ORIGINAL
         ret                  ;hl = PASSWORD
 
    aS EXPLAINED ABOVE, THERE IS NO WAY TO TAKE THE TWO BYTE PASSWORD AND COMPUTE THE ORIGINAL STRING THAT WAS USED TO CREATE IT.  wE CAN, HOWEVER, USE THE BLAZING SPEED OF THE COMPUTER TO COMPUTE A STRING THAT WILL CODE INTO THE SAME TWO BYTES.  wE WILL START WITH A STRING OF 8 BYTES OF SPACES, CODE IT, COMPARE TO WHAT WE WANT.  iF THIS IS NOT CORRECT, WE WILL INCREMENT TO THE NEXT PASSWORD AND TRY AGAIN.  tHIS MAY SEEM LIKE IT WOULD TAKE QUITE SOME TIME, BUT AT THE MACHINE LANGUAGE SPEED, IT WILL NORMALLY COME UP WITH A VALID PASSWORD WITHIN 60 SECONDS.  wE WILL SUPPLY THE PASSWORD WE WANT TO DECODE IN THE hl REGISTERS, AND WE WILL RETURN de WITH THE ORIGINAL PASSWORD, AND A 9 BYTE STRING POINTED TO BY THE hl REGISTERS, AN 8 BYTE PASSWORD, AND AN 03 TERMINATOR WHICH MAKES IT EASY TO PRINT THE STRING ONCE COMPUTED.
 
@decode  ex        de,hl      ;de = CODED PASSWORD
         ld        hl,codstr  ;POINT TO CODE STRING
decodlp  push      hl         ;SAVE STRING POINTER
         call      @code      ;CODE IT INTO hl
         or        a          ;CLEAR CARRY FLAG
         sbc       hl,de      ;SEE IF THE ONE WE WANT
         pop       hl         ;GET STRING POINTER BACK
         ret       z          ;HAVE IT, hl=> STRING
         call      nexcod     ;MAKE IT THE NEXT PASSWORD
         jr        decodlp    ;GO SOME MORE
nexcod   ld        a,(hl)     ;GET THE FIRST BYTE
         cp        ' '        ;STARTING SPACE?
         call      z,newcod1  ;RESTART WITH a-1
         inc       a          ;BUMP IT
         cp        'z'+1      ;STILL IN RANGE?
         ld        (hl),a     ;PUT IT BACK
         ret       c          ;FIRST ONE MUST BE ALPHA
         push      hl         ;SAVE START OF STRING
decdlp   ld        (hl),a     ;UPDATE THE CHARACTER
         inc       hl         ;BUMP THE STRING POINTER
         ld        a,(hl)     ;GET A BYTE
         cp        ' '        ;EMPTY?
         call      z,newcod2  ;RESTART IT WITH '0'-1
         inc       a          ;ADD ONE
         cp        '9'+1      ;STILL 0-9?
         jr        c,decret   ;ok
         cp        'a'        ;BETWEEN ALPHA AND NUMERIC?
         call      c,newcod3  ;START WITH AN a IF YES
         cp        'z'+1      ;AT END OF ALPHA?
         jr        c,decret   ;DONE
         ld        (hl),'0'   ;RESTART IT
         jr        decdlp     ;NEXT POSITION
decret   pop       hl         ;RESTORE TO START OF STRING
         ret
newcod1  ld        a,'a'-1    ;START FOR FIRST CHAR
         ret
newcod2  ld        a,'0'-1    ;START FOR OTHER CHARS
         ret
newcod3  ld        a,'a'      ;START FOR 9-a TRANSITION
         ret
codstr   db        '        ',3 ;WILL USE THIS FOR DECODING
 
    aNOTHER PLACE WHERE A 'CODING' SCHEME IS USED IS IN THE hit TABLE MAINTAINED BY dos.  tHIS CODING IS TERMED A 'HASH' CODE IN THIS PARTICULAR INSTANCE.  iT IS ANOTHER TECHNIQUE USED TO REDUCE THE NUMBER OF CHARACTERS IN A STRING TO A SINGLE BYTE VALUE WHICH IS EASILY STORED IN A MINIMAL AMOUNT OF SPACE.  a FILESPEC MAY BE FROM 1-11 CHARACTERS LONG (8 BYTE FILENAME PLUS A 3 BYTE EXTENSION) THAT NEEDS TO BE STORED IN THE DISKETTE DIRECTORY.  sINCE OTHER INFORMATION NEEDS TO BE KEPT ON A FILE BESIDES THE FILESPEC, dos WILL USUALLY ALLOCATE 32 OR 48 BYTES TO EACH DIRECTORY 'RECORD', THEREBY LIMITING THE NUMBER OF RECORDS PER SECTOR TO FIVE OR EIGHT.  sINCE AN NORMAL DIRECTORY MAY CONTAIN UPWARDS OF 64 DIFFERENT DIRECTORY RECORDS, MANY SECTORS MAY NEED TO BE READ TO LOCATE A FILE THAT NEEDS TO BE ACCESSED.  iNSTEAD OF MANUALLY SEARCHING EACH NAME ONE AT A TIME, dos KEEPS A SINGLE BYTE 'HASH CODE' IN A TABLE USUALLY LOCATED IN THE SECOND SECTOR OF THE DIRECTORY.  tHE POSITION OF THE BYTE IN THE TABLE IS DIRECTLY RELATED TO THE LOCATION OF THE ASSOCIATED FILES DIRECTORY RECORD.  uSING THIS 'HASH TABLE' TECHNIQUE, A MINIMAL AMOUNT OF DIRECTORY RECORDS NEED TO BE SCANNED TO LOCATE THE DESIRED FILE.
    sIMILAR TO PASSWORD ENCODING, THIS IS A ONE WAY PROCESS, THAT IS, A HASH CODE MAY NOT BE RE-CONVERTED TO REVEAL THE EXACT FILE NAME THAT WAS USED TO CREATE IT.  mANY DIFFERENT FILESPECS MAY CONVERT TO THE SAME HASH BYTE, BUT EVEN SO, THE NUMBER OF DUPLICATE BYTES NORMALLY FOUND IN A hit TABLE WILL BE FAR LESS THAN THE AVERAGE NUMBER OF DIRECTORY RECORDS THAT WOULD OTHERWISE NEED TO BE SCANNED TO LOCATE THE FILE.
    oN ENTRY TO THE ROUTINE, WE WILL ASSUME THAT hl POINTS TO AN 11 BYTE FILENAME CORRECTLY ALIGNED TO NAME AND EXTENSION AND EACH PADDED WITH SPACES ON THE RIGHT.  tHIS IS THE NORMAL FORMAT AS THE NAME IS STORED IN A DIRECTORY.  tHE a REGISTER WILL BE RETURNED WITH THE HASH CODE, AND THE OTHER REGISTERS UNCHANGED.  sINCE A ZERO BYTE IN THE hit TABLE INDICATES AN INACTIVE DIRECTORY RECORD, IT MUST NOT BE RETURNED.  iF THE HASHING CODE RESULTS IN A VALUE OF ZERO FOR A FILESPEC, IT WILL BE INCREMENTED TO ONE SO THAT 0 WILL NOT BE RETURNED.
 
@hash    push      hl         ;SAVE POINTERS
         push      bc         ;SAVE FOR COUNTER
         ld        bc,0b00h   ;b = 11 COUNTER, c = START hash
hashlp   ld        a,(hl)     ;GET A BYTE FROM STRING
         inc       hl         ;BUMP POINTER TO NEXT BYTE
         xor       c          ;'HASH' IT
         rlca
         ld        c,a        ;SAVE THE RESULT HERE
         djnz      hashlp     ;DO ALL 11 CHARACTERS
         or        a          ;SEE IF 0 NOW
         jr        nz,hashok  ;NON-ZERO, KEEP IT
         inc       a          ;ELSE MAKE IT ONE
hashok   pop       bc         ;CAN RESTORE NOW
         pop       hl         ;POINTER BACK AT START
         ret                  ;a = HASHCODE
 
    sOMETIMES A PROGRAM MAY NEED A RANDOM NUMBER DURING SOME POINT OF ITS PROCESSING.  tHERE IS NO SUCH THING AS A COMPLETELY RANDOM NUMBER IN THE trs80, AND BECAUSE OF THIS, THE TERM IS CORRECTLY CALLED 'PSEUDO-RANDOM'.  tHERE IS NO HARDWARE 'RANDOM NUMBER GENERATOR' SO THIS FUNCTION MUST BE IMPLEMENTED IN SOFTWARE.  sINCE THERE IS NOTHING 'RANDOM' TO START WITH, NORMALLY THE NEXT BEST THING IS USED, THE 'REFRESH REGISTER'.  tHIS IS A REGISTER IN THE z80 THAT IS USED TO REFRESH THE DYNAMIC MEMORY (ram) IN THE COMPUTER.  tHIS REGISTER IS CONSTANTLY BEING INCREMENTED AS IT MAKES ITS APPOINTED ROUNDS ADDRESSING ALL AVAILABLE MEMORY.  sINCE IT WORKS OFF OF THE SYSTEM CLOCK, IT IS INCREMENTED AT EXACT INTERVALS, AND THEREFORE, IS NOT REALLY 'RANDOM'.  hOWEVER, IT OPERATES AT ITS STEADY PACE REGARDLESS OF WHATEVER TASKS ARE BEING EXECUTED BY THE cpu.  iF WE USE THIS REGISTER AS A STARTING POINT, NORMALLY WE WILL NOT LOAD THE SAME BYTE REPEATEDLY.  iF THIS VALUE IS FURTHER USED AS AN INDIRECT POINTER TO MEMORY, WE MAY GENERATE MORE THOROUGH RANDOMNESS.  tHIS ROUTINE IS OFFERED MERELY AS AN EXAMPLE OF HOW PSEUDO-RANDOM NUMBERS MAY BE GENERATED, AN BY NO MEANS IS MEANT TO REPRESENT A SOPHISTICATED TECHNIQUE.
 
@random  push      hl         ;SAVE THE REGISTERS WE WILL USE
         push      bc
         ld        a,r        ;GET REFRESH REGISTER
         and       3fh        ;REDUCE THE SIZE OF THE NUMBER
         ld        b,a        ;SAVE IT HERE
         djnz      $          ;LET 'RANDOM' CLOCK PERIODS PASS
         ld        a,r        ;REFETCH THE REGISTER
         and       3fh        ;MAKE LESS THAN 40h
         ld        h,a        ;FOR INDIRECT ADDRESS TO rom
         ld        a,r        ;FETCH AGAIN
         xor       h          ;MASK WITH LAST RESULT
         ld        l,a        ;hl => PSEUDO RANDOM ADDRESS
         ld        a,r        ;GET AGAIN
         xor       (hl)       ;MASK WITH INDIRECT ADDRESS
         ld        l,a        ;SAVE INTO LOW ADDRESS
         xor       (hl)       ;JUST FOR GOOD MEASURE
         pop       bc         ;RESTORE STACK
         pop       hl
         ret                  ;a = PSEUDO RANDOM NUMBER
 
    tWO OTHER USEFUL ROUTINES THAT ARE OCCASIONALLY NEEDED ARE SIMPLE FOR SIMPLE INTEGER MATH.  aDDING AND SUBTRACTING CAN BE DONE WITH cpu OPCODES, BUT MULTIPLY AND DIVIDE MUST BE PERFORMED USING REPEATED ADDITION AND SUBTRACTION.  fOR THE MULTIPLY SUBROUTINE WE WILL ASSUME THAT hl CONTAINS THE MULTIPLICAND, AND a CONTAINS THE MULTIPLIER.  tHE RESULTING VALUE WILL BE RETURNED IN A 3 BYTE FIELD OF ahl.  fOR THE DIVIDE, hl WILL CONTAIN THE DIVIDEND, AND a THE DIVISOR.  tHE RESULT WILL BE RETURNED IN A 3 BYTE FIELD WHERE hl EQUALS THE RESULTANT VALUE, AND a WILL CONTAIN THE REMAINDER.
 
@mult    push      bc         ;SAVE REGISTERS WE NEED TO USE
         push      de
         ex        de,hl      ;de = MULTIPLICAND
         ld        c,a        ;SAVE MULTIPLIER HERE
         ld        hl,0       ;STARTING VALUE FOR RESULT
         xor       a          ;CLEAR ALL BITS FOR CARRY CATCHER
         ld        b,8        ;GO FOR 8 BITS IN THE MULTIPLIER
multlp   add       hl,hl      ;MULTIPLY TIMES 2 FOR EVEN BITS
         rla                  ;SAVE CARRY INTO HIGHEST BYTE
         rlc       c          ;SEE IF A BIT SET IN MULTIPLIER
         jr        nc,multnx  ;NOT SET, CONTINUE
         add       hl,de      ;ADD ORIGINAL NUMBER FOR ODD
         adc       a,0        ;CATCH CARRY INTO HIGHEST BYTE
multnx   djnz      multlp     ;GO FOR ALL 8 BITS
         pop       de         ;RESTORE STACK
         pop       bc
         ret                  ;ahl = NUMBER
 
@divide  push      de         ;SAVE NEEDED REGISTERS
         ld        d,a        ;SAVE DIVISOR
         ld        e,16       ;16 BITS TO DIVIDE
         xor       a          ;INITIALIZE FOR DIVIDE
divid1   add       hl,hl      ;GENERATE CARRY FROM HIGH BIT
         rla                  ;MOVE INTO a REGISTER
         jr        c,divid2   ;GO TO RE-CONVERT IF CARRY
         cp        d          ;SEE IF GREATER THAT DIVISOR
         jr        c,divid3   ;NO NEED TO CONVERT
divid2   sub       d          ;REMOVE DIVISOR FROM RESULT
         inc       l          ;ADD ONE FOR COMPLEMENT
divid3   dec       e          ;MORE LEFT?
         jr        nz,divid1  ;FINISH ALL 16 BITS
         pop       de         ;RESTORE STACK
         ret                  ;hl=RESULT, a=REMAINDER
 
    nOW THAT WE HAVE SOME OF THE MORE COMMON SUBROUTINES OUT OF THE WAY, LETS INVESTIGATE SOME OF THE MOST USEFUL FEATURES OF THE trs80, NAMELY ITS ABILITY TO READ AND WRITE TO MAGNETIC DISK MEDIA.  tHIS MAY BE RIGID OR FLOPPY DRIVES, BUT ONLY THE FLOPPY DRIVES WILL BE COVERED HERE.
    tHE FIRST THING WE NEED TO KNOW IS WHICH DRIVE WE WANT TO ACCESS.  uSING BINARY NUMBERS, WE KNOW THAT THE DRIVES ARE NUMBERED FROM 0 TO 3.  iN ORDER FOR THE FLOPPY DISK CONTROLLER TO KNOW WHICH DRIVE WE WANT TO SELECT, WE MUST FIRST CONVERT OUR BINARY DRIVE NUMBER TO A BIT PATTERN THAT THE HARDWARE CAN USE.  iN THE FOLLOWING SUBROUTINE, WE WILL ASSUME THAT THE a REGISTER CONTAINS A BINARY DRIVE NUMBER FROM 0-3 (WE WILL PRODUCE A mod 3 RESULT BY MASKING OFF THE TOP 6 BITS WHICH should BE ZEROES UPON ENTRY), AND WE WILL RETURN WITH THE a REGISTER CONTAINING THE CORRECT BIT PATTERN TO SELECT THE DESIRED DRIVE.  fOR DRIVE 0, BIT 0 MUST BE SET, FOR DRIVE 1, BIT 1 MUST BE SET, ETC.  sINCE BOTH THE BINARY DRIVE NUMBER AND THE RESULTING BIT PATTERN WILL BOTH BE NEEDED BY OTHER ROUTINES, LETS SAVE BOTH OF THE VALUES INTO MEMORY ADDRESS THAT WE WILL DEFINE LATER.  wE WILL STORE THE BINARY DRIVE NUMBER INTO THE VARIABLE (drive), AND THE RESULTING BIT PATTERN INTO VARIABLE (driv).
 
@setdrv  push      bc         ;SAVE REGISTERS WE NEED TO USE
         ld        c,1        ;STARTING BIT MASK
         and       3          ;MASK OFF TOP 6 BITS OF REQUEST
         ld        (drive),a  ;SAVE BINARY DRIVE NUMBER
setdlp   jr        z,setdon   ;DONE IF WE HAVE 0 LEFT
         sla       c          ;MOVE THE BIT OVER ONE PLACE
         dec       a          ;DECREMENT THE BINARY DRIVE
         jr        setdlp     ;GO FOR MORE
setdon   ld        a,c        ;GET THE RESULTING BIT PATTERN
         pop       bc         ;RESTORE THE STACK
         ld        (driv),a   ;SAVE RESULTING BIT PATTERN
         ret
 
    tHE RESULTING BIT PATTERN THAT IS RETURNED IN THE a REGISTER MAY BE DIRECTLY LOADED INTO THE fdc DRIVE SELECT REGISTER AS FOLLOWS:
 
         if        mod1
         ld        (37e1h),a  ;GIVE TO DRIVE SELECT REGISTER
         endif
         if        mod3
         out       (0f4h),a   ;TO SELECT REGISTER
         endif
 
    wE NOW HAVE JUST TURNED ON THE DRIVE THAT WAS ORIGINALLY SUPPLIED IN THE a REGISTER TO @setdrv.  tHIS IS JUST FINE, BUT OTHER FACTORS NEED TO BE CONSIDERED.  mOST IMPORTANT, WE NEED TO KNOW IF WE JUST TURNED ON THE DRIVE MOTOR, OR IF IT WAS ALREADY ON.  aLL OF THE DISK DRIVE MOTORS ARE CONNECTED TO THE SAME SELECT LINE, SO IF ANY DRIVE IS ON, THEY ALL ARE.  tHE FIRST TIME WE TURN ON A DRIVE, WE MUST WAIT A MEASURED AMOUNT OF TIME TO BE SURE THAT THE MOTOR HAS ATTAINED ITS CORRECT SPEED BEFORE WE ATTEMPT TO PERFORM i/o ON IT.  iF THE DRIVE WAS ALREADY ON, THEN NO EXTRA DELAY IS REQUIRED.  wE CAN DETERMINE IF THE MOTOR IS ALREADY ON BY READING THE fdc STATUS REGISTER BEFORE PERFORMING THE SELECT OPERATION.  tHE MOTOR STATUS IS A DIRECT RELATION TO BIT 7 OF THIS REGISTER.  sINCE THIS IS A VERY USEFUL SERVICE, AND WILL BE USED BY MAY OTHER SUBROUTINES, WE WILL MAKE IT A SUBROUTINE ALSO.  wE WILL ASSUME THE A CALL WAS MADE TO @setdrv ABOVE TO SETUP THE CORRECT BIT PATTERN FOR THE DRIVE, AND THAT (driv) NOW CONTAINS THAT VALUE.
 
@select
         if        mod1
         ld        a,(37ech)  ;READ CURRENT fdc STATUS
         endif
         if        mod3
         in        a,(0f0h)   ;READ fdc STATUS
         endif
         and       80h        ;CHECK IF BIT 7 IS SET
         ld        a,0        ;GET THE BINARY DRIVE NUMBER
driv     equ       $-1        ;SETUP VIA @setdrv
         if        mod1
         ld        (37e1h),a  ;GIVE TO DRIVE SELECT REGISTER
         endif
         if        mod3
         out       (0f4h),a   ;GIVE TO SELECT REGISTER
         endif
         ret       z          ;MOTOR IS ALREADY ON, DRIVE IS SELECTED
         push      bc         ;SAVE FOR DELAY COUNTER
         ld        bc,0       ;SETUP DELAY (ONE SECOND)
         call      60h        ;USE rom COUNTDOWN
         pop       bc         ;ONE SECOND NOW PASSED
         if        mod1
         ld        a,(37ech)  ;GET NEW DRIVE STATUS
         endif
         if        mod3
         in        a,(0f0h)   ;NEW DRIVE STATUS
         endif
         and       80h        ;CHECK BIT 7
         ret                  ;z = ok, nz = NO DRIVE
 
    iN THE ABOVE SUBROUTINE, AFTER THE bc REGISTER HAS BEEN DECREMENTED, AND THE THE FULL SECOND HAS BEEN WAITED, THE STATUS HAS BEEN READ AGAIN.  tHIS IS TO ASSURE THE CALLING ROUTINE THAT THE DRIVE SELECT HAS BEEN EXECUTED WITHOUT ERROR.  iF THE STATUS OF THE DISK DRIVE REMAINS nz AFTER THE FULL SECOND HAS BEEN WAITED, THEN THE DISK DRIVE MOTOR HAS NOT BEEN SUCCESSFULLY BEEN ACTIVATED, AND NO i/o MAY BE PERFORMED ON THIS DRIVE.  cHANCES ARE THAT THERE ARE NO DRIVES ATTACHED TO THE SYSTEM, OR THE POWER SWITCHES HAVE NOT BEEN TURNED ON.  iF THE USER IS CERTAIN THAT DRIVES ARE IN THE SYSTEM, A BRANCH MAY BE MADE BACK TO @select AFTER THE bc REGISTER HAS BEEN 'POPPED' BACK OFF THE STACK.  tHIS WILL RE-SELECT THE DRIVE, BUT IT WOULD IMMEDIATELY RETURN WITH THE FIRST CHECKING OF BIT 7.
    sOME HARDWARE MANUFACTURERS HAVE DESIGNED 'HIGH SPEED CLOCKS' THAT MAY INCREASE THE OVERALL SPEED OF THE cpu BY A CERTAIN FACTOR, USUALLY DOUBLE THE NORMAL SPEED.  sINCE THE HIGHSPEED CLOCK WILL MAKE all FOREGROUND OPERATIONS RUN AT THE INCREASED SPEED, THE ABOVE ONE SECOND DELAY WOULD BE REDUCED BY A PROPORTIONAL FACTOR, AND THE MOTOR MAY OR MAY NOT HAVE YET ACHIEVED THE CORRECT SPEED.  iF A HIGH SPEED CLOCK HAS BEEN INSTALLED ON THE SYSTEM, THEN AN EXTRA DELAY WILL BE NEEDED TO WAIT FOR THE CORRECT MOTOR ON WAIT PERIOD.  tHIS IS ONE OF THE TWO CRITICAL AREAS OF DISK i/o THAT HIGH SPEED CLOCKS AFFECT.
 
    wE HAVE NOW ESTABLISHED WHICH DRIVE IS TO BE USED, AND INFORMED THE fdc WHERE IT IS ON THE CIRUITRY.  aNOTHER FACTOR THAT CAN AFFECT OUR DISK i/o OPERATION IS WHETHER OR NOT WE DESIRE THE TRANSFER TO BE MADE IN SINGLE OR DOUBLE DENSITY, AND/OR SINGLE OR DOUBLE SIDED MEDIA.  tHE mOD iii HAS HARDWARE BUILT INTO THE SYSTEM THAT WILL ALLOW ALL TYPES OF MEDIA; SINGLE/DOUBLE DENSITY, SINGLE/DOUBLE SIDED.  tHE mOD i AS SUPPLIED BY rADIO sHACK IS CAPABLE ONLY OF SINGLE DENSITY TO SINGLE SIDED MEDIA.  oTHER VENDORS HAVE SUCCESSFULLY CONNECTED ADDITIONAL HARDWARE TO THE mOD i TO ADD BOTH DOUBLE DENSITY AND DOUBLE SIDED SUPPORT.  dUE TO THE FACT THAT DIFFERENT VENDORS HAVE DESIGNED THE ADDITIONAL HARDWARE DIFFERENTLY, THERE ARE DIFFERENT WAYS THAT THE SOFTWARE WILL 'TALK' TO THE fdc IN THESE EXPANDED MODES.
    aS MENTIONED ABOVE, THE mOD iii COMES STANDARD WITH BOTH DOUBLE DENSITY AND DOUBLE SIDED CONNECTIONS TO THE DISK DRIVES.  tHE DRIVE SELECT ADDRESS (PORT f4h) ALSO CONTROLS THESE OPTIONS.  iF BIT 7 IS SET, DOUBLE DENSITY OPERATION IS SELECTED, IF RESET, THE OPERATION WILL BE IN SINGLE DENSITY.  iF BIT 4 IS SET, THE OPERATION WILL BE ON SIDE 1 OF THE DISKETTE (THE FRONT SIDE), IF RESET, THE OPERATION WILL BE ON SIDE 0 (THE BACK SIDE).  aS MENTIONED ELSEWHERE IN THIS MANUAL ABOUT DISKETTE STRUCTURE, THE 'HIGHER' TRACKS (CLOSER TO THE CENTER OF THE DISKETTE) WILL HAVE LESS AND LESS SURFACE ON WHICH TO CONTAIN THE SAME AMOUNT OF DATA.  tHE mOD iii HAS A HARDWARE FEATURE CALLED 'WRITE PRE-COMPENSATION' THAT IS USED DURING A WRITE OPERATION TO PACK THE DATA CLOSER TOGETHER.  tHIS IS NORMALLY ONLY USED ON TRACKS ABOVE 21 (22 ON UP).  sETTING BIT 5 ACTIVATES THE WRITE PRE-COMP FEATURE, AND RESETTING THE BIT WILL DE-ACTIVE IT.  iN ADDITION TO ALL OF THESE FEATURES, THE DISK DRIVE MAY ALSO SET THE cpu INTO A 'WAIT STATE'.  wHEN THE cpu IS HELD IN THIS STATE, IT WILL ACTUALLY wait (EXECUTE nop'S) UNTIL A BYTE IS READY TO BE TRANSFERRED.  tHIS ALLOWS PERFECT SYNCHRONIZATION OF THE cpu AND fdc DURING DISK i/o TRANSFERS.  sETTING BIT 6 WILL PUT THE cpu INTO WAIT STATES, RESETTING THIS BIT WILL ALLOW NORMAL SOFTWARE EXECUTION.  aS MENTIONED ABOVE, BITS 3 THROUGH 0 ARE USED TO INDICATE WHICH OF THE FOUR DISK DRIVES ARE TO BE USED FOR THE OPERATION.
    fOR SECTOR OPERATIONS THAT WILL BE DEFINED SHORTLY, WE WILL BE USING THE d REGISTER TO INDICATE THE TRACK NUMBER THAT WE WANT TO READ, AND THE e REGISTER TO INDICATE THE SECTOR NUMBER.  sINCE WE HAVE ADDED MANY NEW PARAMETERS TO OUR DRIVE SELECT ROUTINE, LET'S IMPLEMENT ALL THESE FEATURES ON THE mOD iii, AND RE-DEFINE @setdrv TO MAKE USE OF THEM.  tHE SUBROUTINE @select WILL NOT BE AFFECTED BY THIS CHANGE.  wE WILL ASSUME THAT a EQUALS THE BINARY DRIVE NUMBER WE WANT, AND d CONTAINS THE TRACK NUMBER.  iN ADDITION, WE NEED TO PASS PARAMETERS TO INDICATE WHETHER WE WANT SINGLE OR DOUBLE DENSITY, OR SIDE 0 OR 1 OF THE DISKETTE.  wE WILL ASSUME THAT REGISTER a' WILL CONTAIN THE CORRECT BIT MASK FOR THESE SETTINGS.  sETTING BIT 7 FOR DOUBLE DENSITY, AND SETTING BIT 4 FOR SIDE 1 OF THE DISK.
 
@setdrv  push      bc         ;SAVE WHAT WE NEED
         ld        c,1        ;SETUP BIT MASK FOR BIT 0
         and       3          ;MASK UPPER 6 BITS OFF
         ld        (drive),a  ;SAVE BINARY DRIVE NUMBER
setdlp   jr        z,setdon   ;HAVE THE CORRECT BIT
         sla       c          ;MOVE BIT MASK TO THE LEFT
         dec       a          ;REDUCE BINARY DRIVE NUMBER
         jr        setdlp     ;CHECK THE NEXT POSITION
setdon   ex        af,af'     ;GET MASK FOR DENSITY/SIDE
         or        c          ;COMBINE WITH DRIVE PATTERN
         ld        (driv),a   ;SAVE INTO CODE FOR SELECT
         pop       bc         ;RESTORE STACK
         ret                  ;a = BIT PATTERN FOR DRIVE
 
    tHE DENSITY AND SIDE SELECT ON THE mOD i IS COMPLETELY DIFFERENT.  aT THE TIME OF THIS WRITING, rADIO sHACK DOES NOT PROVIDE SUCH HARDWARE, BUT RUMORS INDICATE THAT THEY WILL IN THE NEAR FUTURE.  sEVERAL OTHER HARDWARE VENDORS, THOUGH, HAVE SUCCESSFULLY IMPLEMENTED THESE FEATURES, AND FORTUNATELY, HAVE REMAINED CONSISTENT WITH EACH OTHER AS FAR AS SOFTWARE ACCESS IS CONCERNED.  wE WILL DISCUSS THESE SUCH BOARDS.
    nORMALLY, THE fdc COMMAND REGISTER IS USED TO SELECT THE DENSITY OF THE OPERATION DESIRED.  tWO OF THE COMMANDS THAT ARE NOT A NORMAL PART OF THE fdc LANGUAGE ARE USED TO PASS THESE PARAMETERS TO THE CONTROLLER CHIP.  pLACING AN ffh INTO THE COMMAND REGISTER WILL SELECT DOUBLE DENSITY, AND AN feh WILL SELECT SINGLE DENSITY.  sINCE THE 'HEAD SELECT' LINE HAS NOT BEEN IMPLEMENTED IN THE mOD i, THE HARDWARE VENDORS HAVE COMMONLY DECIDED TO USE THE SELECT LINE THAT IS NORMALLY USED FOR dRIVE 3 TO INDICATE THE HEAD SELECT.  tHIS MEANS THAT A MAXIMUM OF THREE DOUBLE HEADED DRIVES MAY BE CONNECTED TO THE mOD i.  dUE TO THE COMPLEXITY OF THIS, WE AGAIN NEED TO RE-DEFINE OUR @setdrv ROUTINE FOR THE mOD i ALSO.  wE WILL ASSUME THE SAME ENTRY PARAMETERS THAT WERE USED FOR THE mOD iii ROUTINE ABOVE.
 
@setdrv  push      bc         ;SAVE REGISTERS WE NEED TO USE
         ld        c,1        ;SETUP BIT MASK FOR BIT 0
         and       3          ;REMOVE HIGH 6 BITS FROM BINARY
         ld        (drive),a  ;SAVE INTO CODE
setdlp   jr        z,setdon   ;HAVE THE CORRECT BIT
         sla       c          ;MOVE THE BIT TO THE LEFT
         dec       a          ;DECREMENT THE DRIVE NUMBER
         jr        setdlp     ;CONTINUE
setdon   ex        af,af'     ;GET THE DENSITY/SIDE PARAMETER
         push      af         ;SAVE THE BYTE
         rlca                 ;SEE IF DOUBLE/SINGLE DENSITY
         ld        a,0ffh     ;DOUBLE DENSITY PATTERN
         jr        c,setden   ;HAVE THE DENSITY BYTE
         dec       a          ;ELSE MAKE IT feh FOR SINGLE
setden   ex        af,af'     ;SAVE THE PATTERN HERE
         ld        a,(37edh)  ;READ THE CURRENT TRACK REGISTER
         ex        af,af'     ;GET THE PATTERN BACK
         ld        (37ech),a  ;GIVE TO fdc COMMAND REGISTER
         ld        a,0d0h     ;FORCE INTERRUPT COMMAND
         ld        (37ech),a  ;GIVE TO fdc
         ex        af,af'     ;GET TRACK NUMBER BACK
         ld        (37edh),a  ;PUT INTO NEW fdc
         pop       af         ;GET ORIGINAL BYTE BACK
         and       10h        ;WANT BIT 4 ONLY
         rrca                 ;MOVE TO BIT 3
         or        c          ;SETUP PATTERN WITH BIT
         pop       bc         ;RESTORE STACK
         ld        (driv),a   ;SAVE INTO SELECT CODE
         ret                  ;a = BIT PATTERN
 
    iN THE mOD iii, A SINGLE fdc CONTROLLER IS USED FOR ALL TYPES OF OPERATIONS.  iN THE mOD i, TWO fdc CONTROLLERS ARE USED, ONE FOR SINGLE DENSITY, AND ONE FOR DOUBLE DENSITY.  eACH CONTROLLER HAS IT'S OWN INDEPENDENT 'TRACK REGISTER'.  tHIS VALUE MUST BE PASSED FROM ONE CHIP TO THE OTHER AS INDICATED IN THE ABOVE ROUTINE, OR THE fdc WILL NOT KNOW WHICH TRACK OF THE DISK DRIVE THAT THE HEAD IS CURRENTLY LOCATED OVER.
 
    wE NOW HAVE THE ABILITY TO SELECT THE CORRECT DRIVE THAT WE DESIRE, AND ACTIVE ITS MOTOR.  tHE NEXT STEP IS TO MOVE THE HEAD TO THE CORRECT TRACK ON THE DISK DRIVE.  nORMALLY WE INSTRUCT THE fdc WHICH TRACK THAT WE WANT, AND THEN ISSUE A COMMAND.  tHIS IS FINE, BUT FIRST, WE MUST TELL THE fdc EXACTLY WHICH TRACK THE HEAD IS CURRENTLY LOCATED OVER.  tHE fdc HAS ITS OWN INTERNAL REGISTER EXACTLY FOR THIS PURPOSE, BUT THERE IS ONLY ONE REGISTER FOR ALL FOUR DRIVES.  iF WE CHANGE A DRIVE FROM THE LAST ONE ACCESSED, THE TRACK REGISTER IN THE fdc WILL REFLECT THE WRONG NUMBER.  nORMALLY, A 4 BYTE TABLE IS KEPT IN ram THAT WILL STORE THE CURRENT HEAD LOCATION FOR EACH OF THE FOUR DRIVES EACH TIME THAT IT CHANGES.  oNLY THE COMMANDS THAT AFFECT THE HEAD LOCATION NEED TO LOG THIS DATA INTO THE TABLE.  nORMALLY, ALL HEAD MOTION COMMANDS ARE EXECUTED VIA A COMMON CALL, WHICH WE WILL CALL seek.  wE WILL ASSUME THAT A CALL HAS BEEN MADE TO @setdrv TO INITIALIZE THE CORRECT PARAMETERS NEEDED FOR DENSITY/SIDES, AND THAT de CONTAIN THE DESIRED TRACK AND SECTOR NUMBER THAT WE WISH TO READ OR WRITE.  fOR NON SECTOR ORIENTATED OPERATIONS WE DO NOT NEED TO SUPPLY THE SECTOR NUMBER IN e, ONLY THE TRACK NUMBER IN d.
