L10 
W65
U ML ROUTINES U 
 
BY GUY-R. LaForest 
 
Shortly after I bought ADAM, I became very disappointed with the limited capacities of SmartBASIC. Judging from the COLECO games, I knew that SB fell short of utilizing the full capabilities of ADAM. That started my quest for a way to bypass SB's shortcomings and have access to all of ADAM's capacity. That is why I learned how to program in machine language. Mel Ostler has written a fine article on this subject. It should prove to be very interesting and informative. Thanks to his book, LEARNING TO READ WITH ADAM, and Hinkle's HACKER's GUIDE TO ADAM, I now have a fine marriage between SB and ML. 

I like to use SB for programming (especially SB v2.0) and still be able to exploit all of ADAM's fine features. Consequently, I use both SB and ML. Even more interesting is the use of of ML routines that already exist in SB and the EOS which greatly reduce some of the chores in programming. Most of the time, you need only to set up the proper registers with the necessary information and CALL the routine. To better illustrate this, I would like to show you my solution to a particular problem. Don't despair if you don't fully grasp everything the first time. You will still be able to use the routines. 

I was writing a program where the user would have the option of using a drive of his/her choice. The problem was that if the user selected a drive he or she did not have, an empty drive, or bad media, the only feedback would be "I/O Error" and the program would abort. By using the ONERR command, you could prevent the abortion of the program but there would still be no diagnostic features available. Therefore, I decided to write an ML routine that would verify whether the selected drive exists or not and whether media is present or not. If there is a problem, I want the routine to GOTO the appropriate line number in the BASIC program to identify the problem and allow the user to correct it and resume the program. Before presenting you with my solution, I would like to describe the routines CALLed from within this ML program. 
 
Within the upper region of RAM, where the EOS is located, there is a section called the DEVICE CONTROL BLOCK (DCB) which identifies each and every device attached to the ADAM -- from the keyboard to the disk drive. Each device has 21 bytes that identify the device, give pertinent information for the operation of this device, and provide for communication between the Z80 and the device. Most importantly, it also gives us the status of the device. One exception is that both tape drives share the same DCB. When ADAM is first booted, it communicates with all devices to verify if they exist and, if so, what their status is. If the device was not connected to it when ADAM was booted, then no DCB is assigned for the missing device and, as far as ADAM is concerned, it just does not exist. That is why you must turn on your disk drive before booting ADAM since, otherwise, the device missed the roll call and simply isn't there. You can bypass this, but that's a whole different story. 
 
One of the routines in the EOS is named REQUEST STATUS. To use it, you must load register A with the device number and CALL the routine at 64638. Device numbers are: 1- keyboard; 2- printer; 4- Disk Drive #1; 5- Disk Drive #2; 8- Tape Drives (although 24 is used for tape drive #2, the two tape drives share the same DCB); 15- master 6801. The routine scans the DCBs and if not found, it returns with an NZ condition. If found, then Z is set and the register pair IY points to the first byte of the DCB for the requested device. At (IY+20), or the 21st byte of the DCB for the device, is the Node Type Byte. For drives, it tells us if a media is present. The value that the Note Type Byte will assume is according to the following table. The untitled column shows the actual value contained in the Node Type Byte. If this value is loaded into the A register and the command AND 15 is given, then A will show the value in the AND15 column. If four consecutive SRL A commands are given instead of AND 15, then the value in column 4SRLA will be in register A. the purpose of this will become clearer later. However, the reason for the diverse codes for the tape drives is that, as previously mentionned, they both share the same DCB and it is necessary to have all of these codes to be able to determine which tape drive exists (none, #1, #2, or #1 and #2) and which have media and which not.


                    UWITH MEDIA WITHOUT MEDIA U 
 
UDRIVE ____ AND15 4SRLA ____ AND15 4SRLAU 
 
Disk #1 64 0 67 3 
 
Disk #2 64 0 67 3 
 
Tape #1 Only 64 0 4 67 3 4 
 
Tape #2 Only 4 4 0 52 4 3 
 
Tape#1 & #2 (Tape#1)48 0 3 

            (Tape#2) 3 3 0

            (Both) 0 0 0 51 3 3
 
 
With the above information, we'll be able to write an ML routine that will verify if the drive exists and if media is present. But, what happens if there is a problem? How do we tell ADAM to GOTO one line number in BASIC if the drive does not exist and to another line number if media is not present? Actually, this can be done quite easily if we use the GOTO routine used by SB to execute that command. 
 
This routine is located at 8342 to 8380 (9061 to 9102 in v2.0). The first part of the routine which, for our purposes, we will skip, is mostly concerned with loading BC register with the line number to GOTO. The rest of the routine verifies if this line number exists (prints "Undefined statement" if not found) and finds the address in RAM of the actual line to be executed. It then returns to the routine that CALLed it in the first place. So now, everything is set up so that the ML routine knows at which line number we wish to GOTO. However, we must still tell it that we wish for it to return to BASIC and execute the specified line number. 
 
To do this, we shall use another routine contained in SB named EXECUTE LOOP. It is located at 6190 to 6246 in RAM (7014 to 7068 in v2.0). This is the primary routine responsible for the execution of BASIC commands. If the GOTO command had been executed from within a BASIC program, it would have RETurned to the EXECUTE LOOP at 6216 (7038 in v2.0). So all we need to do is JumP to that address and the BASIC program will execute starting from there. 
 
So now, we are just about ready to start writing the ML routine. Please note that all codes and addresses are in decimal since we can then POKE them directly into memory. As most of you know, the largest number that can be POKEd in one byte or one RAM address is 255. However, we must still be able to use numbers larger than this. We can -- up to 65,536. Say that we wanted to represent 8350. We use the following method to find the number to be POKEd in RAM:

  2nd byte = int(number/256) 1st byte = number - (2nd byte*256)

           = int(8350/256) = 8350 -(32*256)

           = 32 = 158 
 
The 1st byte (158), or LOW byte, is POKEd and the 2nd byte (32), or HIGH byte, is POKEd one address higher. Now, let's get to work on writing the ML routine. Things should be clearer then. The column CODES will be the actual numbers that will have to be POKEd into memory while the column MNEMONICS will be the representation of what these codes mean to ADAM.

   UCODES MNEMONICS COMMENTSU 
 
1) 62,0 LD A,0 The device number will
                                      replace the 0.
                                      This is loaded into
                                      register A. 
 
2) 245 PUSH AF Save the device number
                                      on stack. 
 
3) 205,126,252 CALL 64638 Call REQUEST STATUS
                                      routine. 
 
4) 32,39 JR NZ,39 If NZ is shown, the drive 
                                      does not exist. Jump 
                                      routine to line 25. 
 
5) 241 POP AF Loads A with the device
                                      number saved on stack. 
 
6) 254,24 CP 24 If the device number is 24
                                       (tape drive #2), then the
                                      condition will be Z.
                                      Else, NZ 
 
7) 253,126,20 LD A,(IY+20) Load A with the Node Type
                                      Byte. IY points to the 
                                      device's DCB while IY+20
                                      points at the Node Type
                                      Byte within the DCB. 
 
8) 40,11 JR Z,11 If tape drive #2 (see line
                                      6), then Jump Routine to
                                      line 15. Else, continue. 
 
9) 230,15 AND 15 The Node Type Byte in A
                                      assumes the value in 
                                      column AND15 of chart. 
 
10) 254,4 CP 4 If A=4, then Z. Else, NZ.
                                      A will only = 4 if the DCB
                                      shows that, of the two
                                      tape drives, only #2 is 
                                      hooked up. 
 
11) 40,25 JR Z,25 Because of line 4, we will
                                      only be in this part of
                                      the routine if any drive,
                                      other than tape #2, is 
                                      chosen. Jump Routine to 
                                      line 25 if Z (see line 
                                      10). Else, continue. 
 
12) 254,0 CP 0 If A=0 then Z. Else, NZ. A
                                      will = 0 if media is in 
                                      either disk drive or tape
                                      #1. 
 
13) 32,16 JR NZ,16 If no media present, Jump
                                      Routine to line 23. Else,
                                      continue. 
 
14) 201 RET Return to BASIC 
 
15) 203,63 SRL A The routine will have 
                                      jumped here only if tape 
16) 203,63 SRL A #2 was chosen. After line 
17) 203,63 SRL A 18, the Node Type Byte in 
18) 203,63 SRL A A assumes the value in 
                                      column 4SRLA of chart. 
 
19) 254,4 CP 4 If A=4, then Z. Else, NZ.
                                      A will = 4 if, of the two
                                      tape drives, only tape #1
                                      is hooked up. 
 
20) 40,8 JR Z, 8 Because of line 4, we will
                                      only be in this part of
                                      the routine if tape #2 was
                                      chosen. Jump Routine to 
                                      line 25 (see line 19) if 
                                      Z. Else, continue. 
 
21) 254,0 CP 0 If A=0, then Z. Else, NZ.
                                      A will =0 only if media is
                                      present in tape #2 or in
                                      both tape drives. 
 
22) 200 RET Z RETurn to BASIC if media 
                                      is present. Else,
                                      continue. 
 
23) 1,0,200 LD BC,200 Load BC with the BASIC 
                                      line number to GOTO when
                                      there is no media in the
                                      drives. I'm assuming line
                                      number 200 
 
24) 24,3 JR 3 Jump Routine to line 26. 
 
25) 1,44,1 LD BC,300 Load BC with the BASIC 
                                      line number to GOTO when
                                      the requested drive does
                                      not exist. I'm assuming
                                      line number 300. 
 
26) 205,158,32 CALL 8350 Part of GOTO routine to
                                      set up registers. 
 
27) 195,72,24 JP 6216 Part of EXECUTE LOOP. It
                                      will now return to BASIC
                                      at the line number loaded
                                      in the BC registers.
 
 
If you are using SB v2.0, the only modifications to be made are in line 26 and 27. They would be as follows: 
 
26) 205,112,35 CALL 9072 Part of GOTO. 
 
27) 195,126,27 JP 7038 Part of EXECUTE LOOP.
 
 
All that needs to be done now is to use this in a BASIC program. I will give you a sample program that you can use to test this ML routine. For v2.0 users, substitute the underlined numbers in the data statement with the ones in line 26 and 27 above. This ML routine is not address specific. In other words, it can be POKEd anywhere. Just remember not to POKE it over SB or the EOS. The preference is to set LOMEM and POKE it just underneath. I will expand the BASIC program in another article to introduce a few other routines so if you decide to lead this one, I would recommend that you use the same line numbers that I have. To use the ML, POKE the device number at the 2nd byte of the ML routine and CALL the 1st byte. 

1 REM ML routine demo
2 REM By Guy-R. LaForest 89-02-24 
10 LOMEM :28600 
20 DATA 62,0,245,205,126,252,32,39,241,254,24,253,126,20,40,11,230,15,254,4,40,25,254,0,32,16,201 
30 data 203,63,203,63,203,63,203,63,254,4,40,8,254,0,200,1,0,200,24,3,1,44,1 
40 data 205,U158,32,195,72,24U 
41 rem vor v2.0 use 205,112,35,195,126,27, in line 40 
90 for i%=0 to 55: read d%: poke 27410+i%,d%:next 
100?"PLEASE SELECT DRIVE: I TAPE 1":?spc(21);"II TAPE 2" 
110?SPC(21);"III DISK 1":? SPC(21);"IV DISK 2" 
120 GET k$: k% = ASC(k$): if k%<129 or k%>132 then 120 
130 if k%=129 then k%=8:goto 160: rem tape drive #1 
140 if k%=130 then k%=24:goto 160: rem tape drive #2 
150 k%=k%-127: rem both disk drives 
160 poke 27411,k%:call 27410:? "SELECTED DRIVE HAS MEDIA":END: rem Enter the device number in ML routine and verify if drive exists and if media is present. 
200 ?"SELECTED DRIVE HAS NO MEDIA":?:?"SELECT AGAIN":?"GOTO 100 
300 ?"SELECTED DRIVE DOES NOT EXIST":?:?"SELECT AGAIN":?:GOTO 100 
 
2nd byte of the ML routine and CALL the 1st byte. 
 

