THE CENTRAL INPUT-OUTPUT SYSTEM IN ATARI COMPUTERS
In any computer system, the terms input and output refer to communication between the microprocessor and any external device – a keyboard, the screen editor, a printer, a disk drive, a tape recorder, or other similar peripheral. The ATARI operating system contains the routines for interacting with any of these devices at several levels, but many microcomputers have this ability. The aspect of the ATARI system which makes it unique – and, from a programmer's point of view, so easy to use – is that all external devices are handled identically and are differentiated only by changing minor aspects of the input-output routine.
Input is the passage of information from the outside world, for example, from the keyboard, to the microprocessor. Output is the reverse process, whereby information proceeds from the computer to the outside, to a printer, for example. Throughout the remainder of this book, we will refer to the Central Input-Output system as CIO.
VECTORS IN AN ATARI COMPUTER
We mentioned earlier that the techniques and routines used in this book will work with any ATARI computer because the vectors to the routines in the operating system are guaranteed by ATARI not to change. Located within the operating system of your ATARI is a jump table, which contains the addresses of all of the key routines needed for programming in assembly language. The table extends from $E450 through $E47F, and in an ATARI 800 with the B operating system, the table looks like this:
Address |
Contains the Instruction |
|
|
E450 E453 E456 E459 E45C E45F E462 E465 E468 E46B E46E E471 E474 E477 E47A E47D |
JMP $EDEA JMP $EDF0 JMP $E4C4 JMP $E959 JMP $E8ED JMP $E7AE JMP $E905 JMP $E944 JMP $EBF2 JMP $E6D5 JMP $E4A6 JMP $F223 JMP $F11B JMP $F125 JMP $EFE9 JMP $EF5D |
It's easy to see why this is called a jump table, since it is a table of addresses to which program control will jump when accessed. "Why not jump directly to the given address?" you may ask. In the answer lies the key to writing programs which will run on all ATARI computers. Suppose that rather than accessing $E456, we choose to jump directly to location $E4C4, bypassing the jump table. Everything will work fine, and our program will run. But, now suppose that ATARI produces some new computer, the 24800 XLTVB, and the operating system needs to be somewhat altered to accomodate several new features of this magnificent new machine. Our program is in trouble. ATARI never guaranteed that location $E4C4 would stay the same forever; they only guaranteed that the jump table would always point to the right address. That is, if we had accessed $E456 instead of $E4C4, our program would always work, since location $E456 is guaranteed not to change. Let's look at the various vectors in this jump table, with their ATARI equates (the names we'll use for these addresses in any programs we write) and their uses:
Equate
Address
Use
DISKIV $E450 Disk handier initiation routine
DSKINV $E453 Disk handler vector
CIOV $E456 Central Input/Output vector
SIOV $E459 Serial Input/Output vector
SETVBV $E45C Set system timers routine vector
SYSVBV $E45F System vertical blank interrupt processing
XITVBV $E462 Exit from vertical blank processing
SIOINV $E465 Serial Input/Output initialization
SENDEV $E468 Serial bus send enable routine
INTINV $E46B Interrupt handler routine
CIOINV $E46E Central Input/Output initialization
BLKBDV $E471 Blackboard mode vector to memo pad mode
WARMSV $E474 Warm start entry (follows SYSTEM RESET)
COLDSV $E477 Cold start entry point (follows power-up)
RBLOKV $E47A Cassette read block routine vector
CSOPIV $E47D Cassette open for input vector
DISKIV $E450 Disk handier initiation routine
DSKINV $E453 Disk handler vector
CIOV $E456 Central Input/Output vector
SIOV $E459 Serial Input/Output vector
SETVBV $E45C Set system timers routine vector
SYSVBV $E45F System vertical blank interrupt processing
XITVBV $E462 Exit from vertical blank processing
SIOINV $E465 Serial Input/Output initialization
SENDEV $E468 Serial bus send enable routine
INTINV $E46B Interrupt handler routine
CIOINV $E46E Central Input/Output initialization
BLKBDV $E471 Blackboard mode vector to memo pad mode
WARMSV $E474 Warm start entry (follows SYSTEM RESET)
COLDSV $E477 Cold start entry point (follows power-up)
RBLOKV $E47A Cassette read block routine vector
CSOPIV $E47D Cassette open for input vector
We'll be using some of these vectors in the programs we'll write, and some we'll never use, but knowing where they are will help you if you need to make use of them in your own programs. Many are used by the operating system itelf.
To access any of these routines in the operating system, we simply need to JSR to the appropriate address. All of these routines in the operating system are written as subroutines, and therefore end with RTS instructions, which will return control to your program. For instance, to access CIO, we simply need to type
JSR
CIOV
and the job is done. Of course, a considerable amount of setup is required before this call can be made, which we'll be covering shortly; but the actual call to CIO couldn't be simpler.
While we're discussing the available vectors in the operating system, let's briefly cover the RAM and ROM vectors. They are summarized in the following table, with their equates, the information contained in them in the B operating system, and a brief description of their use:
Equate Address Points to
Use
CASINI $0002 varies Bootable cassette init. vector
DOSVEC $000A varies Disk software run vector
DOSINI $000C varies Disk initialization vector
VDSLST $0200 $E7B3 DLI NMI vector
VPRCED $0202 $E7B2 Proceed line IRO vector *
VINTER $0204 $E7B2 Interrupt line IRO vector *
VBREAK $0206 $E7B2 BRK instruction IRQ vector
VKEYBD $0208 $FFBE Keyboard IRQ vector
VSERIN $020A $EB11 Serial input ready IRQ vector
VSEROR $020C $EA90 Serial output ready IRQ vector
VSEROC $020E $EAD1 Serial output done IRQ vector
VTIMR1 $0210 $E7B2 POKEY timer 1 IRQ vector
VTIMR2 $0212 $E7B2 POKEY timer 2 IRQ vector
VTIMR4 $0214 $E7B2 POKEY timer 4 IRQ vector
VIMIRQ $0216 $E6F6 Vector to IRQ handier
VVBLKI $0222 $E7D1 Immediate VBI NMI vector
VVBLKD $0224 $E93E Deferred VBI blank NMI vector
CDTMA1 $0226 varies System timer 1 JSR address
CDTMA2 $0228 varies System timer 2 JSR address
BRKKY $0236 $E754 BREAK key vector only on "B" OS
RUNVEC $02E0 varies Load and go run vector
INIVEC $02E2 varies Load & go initialization vector
CASINI $0002 varies Bootable cassette init. vector
DOSVEC $000A varies Disk software run vector
DOSINI $000C varies Disk initialization vector
VDSLST $0200 $E7B3 DLI NMI vector
VPRCED $0202 $E7B2 Proceed line IRO vector *
VINTER $0204 $E7B2 Interrupt line IRO vector *
VBREAK $0206 $E7B2 BRK instruction IRQ vector
VKEYBD $0208 $FFBE Keyboard IRQ vector
VSERIN $020A $EB11 Serial input ready IRQ vector
VSEROR $020C $EA90 Serial output ready IRQ vector
VSEROC $020E $EAD1 Serial output done IRQ vector
VTIMR1 $0210 $E7B2 POKEY timer 1 IRQ vector
VTIMR2 $0212 $E7B2 POKEY timer 2 IRQ vector
VTIMR4 $0214 $E7B2 POKEY timer 4 IRQ vector
VIMIRQ $0216 $E6F6 Vector to IRQ handier
VVBLKI $0222 $E7D1 Immediate VBI NMI vector
VVBLKD $0224 $E93E Deferred VBI blank NMI vector
CDTMA1 $0226 varies System timer 1 JSR address
CDTMA2 $0228 varies System timer 2 JSR address
BRKKY $0236 $E754 BREAK key vector only on "B" OS
RUNVEC $02E0 varies Load and go run vector
INIVEC $02E2 varies Load & go initialization vector
Those marked with "*" are unused at present. Notice that a number of these vectors point to the same place in the operating system, $E7B3. This is the address of the central interrupt processing routine, which determines the nature of the interrupt and directs program control to the appropriate routines in the operating system to handle that type of interrupt.
These vectors, unlike the ROM vectors, are not arranged in a jump table, so they cannot be accessed by a simple JSR instruction. However, they do point to operating system routines which end in an RTS instruction, so we would like to access them using a JSR instruction. The proper method is to set up a JSR to a location which JMPs indirectly to the above vector. For instance, suppose we want to vector through the DOSINI vector. This is done properly with the following code:
40
JSR MYSPOT
45
50
55
60 MYSPOT JMP (DOSINI)
45
50
55
60 MYSPOT JMP (DOSINI)
Following the JSR to MYSPOT, the RTS in the operating system routine will return control to line 45, at which point your program will resume.
Now that we've seen how to write programs which will work on all ATARI computers, let's discuss the CIO philosophy and learn how to write programs which interact with the real world.
THE INPUT-OUTPUT CONTROL BLOCK (IOCB)
There are two parts to the CIO system in the ATARI. These are the Input-Output Control Block, or IOCB, and the handler table. Let's discuss these one at a time, and then we'll see how they work together to form an operational CIO system.
The IOCB is a section of memory on page 3 which contains the information that is set up by the programmer to tell the ATARI which device is desired and what information is to be passed. Each IOCB requires 16 bytes of information, and 8 IOCBs are available. Their names and locations are as follows:
Name
Location
IOCB0 $340 to 34F
IOCB1 $350 to 35F
IOCB2 $360 to 36F
IOCB3 $370 to 37F
IOCB4 $380 to 38F
IOCB5 $390 to 39F
IOCB6 $3A0 to 3AF
IOCB7 $380 to 3BF
IOCB0 $340 to 34F
IOCB1 $350 to 35F
IOCB2 $360 to 36F
IOCB3 $370 to 37F
IOCB4 $380 to 38F
IOCB5 $390 to 39F
IOCB6 $3A0 to 3AF
IOCB7 $380 to 3BF
Several of these IOCBs are used by the system as defaults, although as programmers we are free either to use these as the system defaults, or to change them to suit our own purposes. In fact, only 3 of them are normally used by the OS; there is generally no need to redefine them, since we have five others from which to choose. The three used by the OS are as follows:
- IOCB0, the screen editor. By directing output to IOCB0, we can have information passed to the screen editor. This IOCB also controls the text window in any of the split-screen graphics modes.
- IOCB6, the screen display for graphics modes higher than zero. This IOCB is used for all graphics commands, like PLOT, DRAWTO, FILL, and others.
- IOCB7, used to support the LPRINT command of BASIC, which directs output to the printer when this command is used. In practice, much output from BASIC directed to a printer uses one of the other IOCBs, since LPRINTs are not frequently used; more formatting is available if a specific IOCB is OPENed for use with a printer.
PRINT
#6;"HELLO"
The 16 bytes of the IOCB, and their offsets from the beginning of the IOCB in use, are described below:
Label | Offset | Length | Description |
|
|||
ICHID ICDNO ICCOM ICSTA ICBAL/H ICPTL/H ICBLL/H ICAX1 ICAX2 ICAX3/4 ICAX5 ICAX6 |
0 1 2 3 4,5 6,7 8,9 10 11 12,13 14 15 |
1 1 1 1 2 2 2 1 1 2 1 1 |
Index
into device name table for this IOCB Device number Command byte: determines action to be taken Status returned by device Two-byte buffer address of stored information Address - 1 of device's put character routine Buffer length First auxiliary byte Second auxiliary byte Auxil. bytes 3 & 4 – for BASIC NOTE and POINT Fifth auxil. byte – for NOTE and POINT also Spare auxilliary byte – unused at present |
A SIMPLE I/O EXAMPLE USING AN IOCB
Before getting into the details of the various bytes required for each possible function of an IOCB, an example program will help in understanding their use. Let's take a simple BASIC example and convert it to its assembly language equivalent. The line of BASIC programming we want to duplicate is:
CLOSE
#4:OPEN #4,6,0,"D:*.*"
For now, we need to know that the command byte stored in ICCOM must be $C for the CLOSE command or 3 for the OPEN command, and OPENing the disk directory requires a 6 in ICAX1. Let's look at the program required to OPEN such a file:
Listing 9.1
0100 ; ******************************
0110 ; First set up equates
0120 ; ******************************
0000 0130 *= $600
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0344 0160 ICBAL = $0344
0345 0170 ICBAH = $0345
034A 0180 ICAX1 = $034A
E456 0190 CIOV = $E456
0200 ; ******************************
0210 ; Now CLOSE #4 for insurance
0220 ; ******************************
0600 A240 0230 LDX #$40 ; #$40 for IOCB #4
0602 A90C 0240 LDA #$C ; CLOSE command byte
0604 9D4203 0250 STA ICCOM,X ; X = IOCB #4
0607 2056E4 0260 JSR CIOV ; Let CIO do the CLOSE
0270 ; ******************************
0280 ; Now we'll open the directory
0290 ; ******************************
060A A240 0300 LDX #$40 ; Again, #$40 = IOCB4
060C A901 0310 LDA #1 ; Disk drive #1
060E 9D4103 0320 STA ICDNO,X ; Put drive # here
0611 A903 0330 LDA #3 ; For OPEN
0613 9D4203 0340 STA ICCOM,X ; Command byte
0616 A906 0350 LDA #6 ; For disk directory
0618 9D4A03 0360 STA ICAX1,X ; Store 6 here
061B A929 0370 LDA #FILE&255 ; See discussion
061D 9D4403 0380 STA ICBAL,X ; Low byte buffer address
0620 A906 0390 LDA #FILE/256 ; See discussion
0622 9D4503 0400 STA ICBAH,X ; High byte buffer address
0625 2056E4 0410 JSR CIOV ; Let CIO OPEN it
0628 60 0420 RTS ; All done
0430 ; ******************************
0440 ; Now we need the filename
0450 ; ******************************
0629 44 0460 FILE .BYTE "D:*.*",$9B
062A 3A
062B 2A
062C 2E
062D 2A
062E 9B
0100 ; ******************************
0110 ; First set up equates
0120 ; ******************************
0000 0130 *= $600
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0344 0160 ICBAL = $0344
0345 0170 ICBAH = $0345
034A 0180 ICAX1 = $034A
E456 0190 CIOV = $E456
0200 ; ******************************
0210 ; Now CLOSE #4 for insurance
0220 ; ******************************
0600 A240 0230 LDX #$40 ; #$40 for IOCB #4
0602 A90C 0240 LDA #$C ; CLOSE command byte
0604 9D4203 0250 STA ICCOM,X ; X = IOCB #4
0607 2056E4 0260 JSR CIOV ; Let CIO do the CLOSE
0270 ; ******************************
0280 ; Now we'll open the directory
0290 ; ******************************
060A A240 0300 LDX #$40 ; Again, #$40 = IOCB4
060C A901 0310 LDA #1 ; Disk drive #1
060E 9D4103 0320 STA ICDNO,X ; Put drive # here
0611 A903 0330 LDA #3 ; For OPEN
0613 9D4203 0340 STA ICCOM,X ; Command byte
0616 A906 0350 LDA #6 ; For disk directory
0618 9D4A03 0360 STA ICAX1,X ; Store 6 here
061B A929 0370 LDA #FILE&255 ; See discussion
061D 9D4403 0380 STA ICBAL,X ; Low byte buffer address
0620 A906 0390 LDA #FILE/256 ; See discussion
0622 9D4503 0400 STA ICBAH,X ; High byte buffer address
0625 2056E4 0410 JSR CIOV ; Let CIO OPEN it
0628 60 0420 RTS ; All done
0430 ; ******************************
0440 ; Now we need the filename
0450 ; ******************************
0629 44 0460 FILE .BYTE "D:*.*",$9B
062A 3A
062B 2A
062C 2E
062D 2A
062E 9B
In both the CLOSE and OPEN parts of the program, we load the X register with #$40, which will act as the offset into IOCB4. (If we wanted to use IOCB3, we'd simply load the X register with #$30, and so on for all of the other IOCBs.) We then store the command byte $C into ICCOM for that IOCB, and a JSR to CIOV accomplishes the CLOSE for us. It's always a good idea to CLOSE a file before OPENing it, just in case it was already open for some other reason. If the file is already open, you'll get an error on the return from CIO. You can check for an error on any call to the OS by branching to some error-handling routine of your own if after the JSR to CIOV, the minus flag in the processor status register is set. Therefore, we should put a BMI ERROR instruction after the JSR CIOV instruction; but for the purposes of this discussion, we'll assume all is well. You should never make that assumption in your own programs, however.
To OPEN the file, we put a 1 into ICDNO for IOCB4, for disk drive 1, and then we put the command byte 3 into ICCOM for IOCB4 and put a 6 into ICAX1. All we have left to do before the call to CIO is to point the buffer address to the name of the file we want to OPEN. This name is located in line 460, and we've given it the label FILE. The $9B following the file name is the hexadecimal code for a carriage return, which should always follow file or device names, such as S: or P:.
To point the buffer to the file name, we need to break its address into low and high bytes. The low byte is the address AND 255, written #FILE&255. The ANDing with 255 ensures that we get only the low byte of the address. The high byte can be obtained by dividing the address by 256, as we did in line 390. The low and high bytes are stored in ICBAL and ICBAH, respectively, and then a call to CIO in line 410 completes the OPEN command for us.
This simple example demonstrates not only how to open a disk directory, but it also shows exactly how every call to the CIO routine in your ATARI is made. We first set up the appropriate bytes in the IOCB and then simply JSR to CIOV to accomplish the task, whether it is to OPEN a file, READ some information from a disk or tape, or send information to a printer. All of these operations are done using this same sequence of events, which makes input and output in assembly language on your ATARI so simple, once you understand the system. Note that not all of the 16 bytes in the IOCB need to be altered to perform a call to CIO. In fact, we shall see that for some commands, one or two of these bytes are all that are necessary for implementation of the function.
DETAILS OF THE BYTES IN AN IOCB
Now that we've seen how to implement a simple call to the central input-output system of the ATARI computers, we'll review the full spectrum of information which needs to be stored in the various locations of the IOCB in order to implement all possible I/O operations. We'll examine each byte of the IOCB, in the order in which they appear.
The first byte, ICHID, acts as an index into the device table, so you can always tell which device an IOCB is accessing by looking at the first byte. This is set by the OS, and you'll not need to set it for any use. The OS determines this index following the OPEN command and stores the appropriate information here.
ICDNO, the device number, is most often used when more than one disk drive is connected to the system. A different IOCB is used to communicate with each disk drive, and byte 2 of the IOCB distinguishes between the drives in use. If a 1 is stored here, the IOCB will access disk drive 1, and similarly for drives 2 through 4.
The command bytes for the various devices which can be connected to your ATARI computer are as follows:
Command
Byte
Description
Open 3 Open the device for operation
Get record 5 Input a line
Get character 7 Input one or more characters
Put record 9 Output a line
Put character 11 Output one or more characters
Close 12 Close the device
Status 13 Get device status
Draw line 17 Draw a line in GRAPHICS modes
Fill command 18 Fill part of GRAPHICS screen with color
Format disk 254 Format disk
Open 3 Open the device for operation
Get record 5 Input a line
Get character 7 Input one or more characters
Put record 9 Output a line
Put character 11 Output one or more characters
Close 12 Close the device
Status 13 Get device status
Draw line 17 Draw a line in GRAPHICS modes
Fill command 18 Fill part of GRAPHICS screen with color
Format disk 254 Format disk
The fourth byte of the IOCB is ICSTA, which is set by the OS following the return from CIO. The status is also set in the Y register upon return from any call to CIO, so either the Y register or ICSTA can be read by your program to determine the success or failure of each I/O operation. Any negative status (value greater than 128 decimal, or $80 hexadecimal) indicates that an error occurred in the I/O operation.
The next 2 bytes of the IOCB act as a pointer to the buffer used for either input or output, and are in the usual 6502 order, low byte first. They are called ICBAL and ICBAH, respectively. A buffer is an area of memory which contains the information you wish to output, or into which you want the input information placed. For instance, if you want to send text to a printer, ICBAL and ICBAH are set up to point to the area of memory containing the text to be printed. If you want to read a disk file into memory, these bytes of the IOCB are set up to point to the area of memory where you want the information placed from the disk.
ICPTL and ICPTH act as another 2-byte pointer, but in this case, they point to the address of the put-byte routine of the device, minus 1. Every device which can be opened for output must have a put-byte routine written for it, telling the computer how to send information to it. This will be covered more completely when we discuss the handler table.
The next 2 bytes of the IOCB are ICBLL and ICBLH, which contain the length of the I/O buffer, in bytes. As we shall see, there is a special case of I/O in which we set the length of the buffer equal to zero, by setting both ICBLL and ICBLH to zero. In this special case, the information transferred is to or from the accumulator, rather than to or from memory.
Since many devices that can be connected to your ATARI have several possible functions, you must be able to define in the IOCB which function is to be implemented. This is done using the byte in ICAX1, the next byte of the IOCB. The following table lists the various possible bytes for ICAX1. TW refers to a separate text window on the screen, such as that set up by the BASIC command GRAPHICS 3; RE refers to a READ operation enabled from the screen; and RD means that such a READ is not allowed, or disabled.
ICAX1
Device Byte Function
Screen editor 8 Output to the screen
12 Input from the keyboard and output to screen
13 Forced screen input and output
Screen display 8 Screen is cleared; no TW; RD
12 Screen is cleared; no TW; RE
24 Screen is cleared; TW; RD
28 Screen is cleared; TW; RE
40 Screen is not cleared; no TW; RD
44 Screen is not cleared; no TW; RE
56 Screen is not cleared; TW; RD
60 Screen is not cleared; TW; RE
Keyboard 4 Read – note: no output is possible
Printer 8 Write – note: no input is possible
Tape recorder 4 Read
8 Write
RS-232 port 5 Concurrent read
8 Block write
9 Concurrent write
13 Concurrent read and write
Disk drive 4 Read
6 Read disk directory
8 Write new file
9 Write – append
12 Read and write – update mode
Device Byte Function
Screen editor 8 Output to the screen
12 Input from the keyboard and output to screen
13 Forced screen input and output
Screen display 8 Screen is cleared; no TW; RD
12 Screen is cleared; no TW; RE
24 Screen is cleared; TW; RD
28 Screen is cleared; TW; RE
40 Screen is not cleared; no TW; RD
44 Screen is not cleared; no TW; RE
56 Screen is not cleared; TW; RD
60 Screen is not cleared; TW; RE
Keyboard 4 Read – note: no output is possible
Printer 8 Write – note: no input is possible
Tape recorder 4 Read
8 Write
RS-232 port 5 Concurrent read
8 Block write
9 Concurrent write
13 Concurrent read and write
Disk drive 4 Read
6 Read disk directory
8 Write new file
9 Write – append
12 Read and write – update mode
The last byte of the IOCB we will discuss here is ICAX2, the second auxiliary byte. ICAX2 is used in only a few special cases; otherwise, it is set to zero. When using the cassette recorder, if a value of 128 is stored in ICAX2, the short interrecord gaps, the silent spaces between sections of information on the tape, are used, which will allow faster loads of a tape written in this manner, A value of zero in ICAX2 will produce the normal, longer interrecord gaps.
Using the ATARI 820 printer, storing a value of 83 in ICAX2 will cause the printer to print sideways instead of in its normal mode. Furthermore, values of 70 or 87 in ICAX2 will produce normal or double-width characters on this printer.
Finally, graphics modes 0 to 11 are specified in the OPEN command by placing the number of the desired mode in ICAX2. In combination with the values described for ICAX1 above, ICAX2 gives the assembly language programmer complete control over the graphics mode, text window, screen clear, and read-write functions of the screen. We'll learn more about this in Chapter 10.
THE HANDLER TABLE
Now that we've covered the various parts of an IOCB, we'll briefly describe the handler table and how it works with the IOCBs to form the I/O system with CIO. Then we'll look at a number of examples which will show how to use this information to perform many different types of I/O from assembly language. The simplest way to examine the handler table is to view it as a short assembly language program, such as this:
0100
PRINTV = $E430
0110 CASETV = $E440
0120 EDITRV = $E400
0130 SCRENV = $E410
0140 KEYBDV = $E420
0150 ; *****************************
0160 ; Origin of HATABS = $031A
0170 ; *****************************
0180 *= $031A
0190 .BYTE "P" ; Printer
0200 .WORD PRINTV ; vector
0210 .BYTE "C" ; Cassette recorder
0220 .WORD CASETV ; vector
0230 .BYTE "E" ; Editor
0240 .WORD EDITRV ; vector
0250 .BYTE "S" ; Screen
0260 .WORD SCRENV ; vector
0270 .BYTE "K" ; Keyboard
0280 .WORD KEYBDV ; vector
0290 .BYTE 0 ; Free entry #1(DOS)
0300 .WORD 0,0
0310 .BYTE 0 ; Free entry #2(850 interface)
0320 .WORD 0,0
0330 .BYTE 0 ; Free entry #3
0340 .WORD 0,0
0350 .BYTE 0 ; Free entry #4
0360 .BYTE 0,0
0370 .BYTE 0 ; Free entry #5
0380 .WORD 0,0
0390 .BYTE 0 ; Free entry #6
0400 .WORD 0,0
0410 .BYTE 0 ; Free entry #7
0420 .WORD 0,0
0110 CASETV = $E440
0120 EDITRV = $E400
0130 SCRENV = $E410
0140 KEYBDV = $E420
0150 ; *****************************
0160 ; Origin of HATABS = $031A
0170 ; *****************************
0180 *= $031A
0190 .BYTE "P" ; Printer
0200 .WORD PRINTV ; vector
0210 .BYTE "C" ; Cassette recorder
0220 .WORD CASETV ; vector
0230 .BYTE "E" ; Editor
0240 .WORD EDITRV ; vector
0250 .BYTE "S" ; Screen
0260 .WORD SCRENV ; vector
0270 .BYTE "K" ; Keyboard
0280 .WORD KEYBDV ; vector
0290 .BYTE 0 ; Free entry #1(DOS)
0300 .WORD 0,0
0310 .BYTE 0 ; Free entry #2(850 interface)
0320 .WORD 0,0
0330 .BYTE 0 ; Free entry #3
0340 .WORD 0,0
0350 .BYTE 0 ; Free entry #4
0360 .BYTE 0,0
0370 .BYTE 0 ; Free entry #5
0380 .WORD 0,0
0390 .BYTE 0 ; Free entry #6
0400 .WORD 0,0
0410 .BYTE 0 ; Free entry #7
0420 .WORD 0,0
Each entry in the handler table consists of the first letter of the specified device, followed by the vector which points to the location in memory of the information needed to deal with that device. As you can see, there are seven free places left in the handler table, so the programmer is free to add whatever devices are necessary for any purpose, and they'll be treated just like the devices already specified. One other very important point about the handler table should be noted here. Whenever the OS looks into the handler table to find out where in memory it needs to look to take care of a particular device, it reads the table from the bottom up! This is intentional and allows you to insert your own printer handler near the bottom of the table. As the table is searched, your vector will be found first, and it is the one that will be used. Therefore, you can write your own printer-handling routines and substitute these for the normal routines easily, simply by placing another P: in one of the lower free entries and following it by the 2-byte address vector pointing to your new handling routines.
Let's briefly look at a typical handler entry point table, which is the table to which the entry in the handler table points. For example, the vector PRINTV, used above, points to a second table, the printer handler entry point table. In fact, all of the above vectors point to their respective handler entry point tables, and all of these tables are arranged identically. They contain the addresses minus 1 of the routines used for the following functions, in the following order:
OPEN the device
routine
CLOSE the device routine
READ routine
WRITE routine
STATUS of the device routine
SPECIAL functions, where implemented
CLOSE the device routine
READ routine
WRITE routine
STATUS of the device routine
SPECIAL functions, where implemented
The handler entry point table is always terminated by a 3-byte JMP instruction, which points to the initialization routine for that device. Remember. the addresses found in the handler entry point table do not point to the OPEN and CLOSE routines, but rather, they point to the address 1 byte lower in memory than the beginning of each of these routines. It is obviously very important to remember this when you are constructing your own handler entry point table!
A SIMPLE I/O ROUTINE
Let's see how we can use CIO for a simple function – writing to the screen. We know that in BASIC, if we want to write a line of text to the screen, all that's required is a single line of code like this:
PRINT
"A SUCCESSFUL WRITE!"
In assembly language, it's also fairly simple to print to the screen, now that we understand the use of the IOCB and CIO. Just to review, we don't have to open the screen as a device if we don't want to, since IOCB0 is already allocated by the OS for the screen. Therefore, we can load the X register with zero and use that as an offset into the IOCB. Alternatively, we can just use absolute addressing, since we'll be using the first IOCB. In the example below, we'll use the X register loaded with zero, just so we become familiar with the normal procedure for inserting the required information into the IOCB. Here's the routine to write the line to the screen:
Listing 9.2
0100 ; *****************************
0110 ; CIO equates
0120 ; *****************************
0340 0130 ICHID = $0340
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0343 0160 ICSTA = $0343
0344 0170 ICBAL = $0344
0345 0180 ICBAH = $0345
0346 0190 ICPTL = $0346
0347 0200 ICPTH = $0347
0348 0210 ICBLL = $0348
0349 0220 ICBLH = $0349
034A 0230 ICAXl = $034A
034B 0240 ICAX2 = $034B
E456 0250 CIOV = $E456
0000 0260 *= $600
0270 ; *****************************
0280 ; Now we load in required data
0290 ; *****************************
0600 A200 0300 LDX #0 ; Since it's IOCB0
0602 A909 0310 LDA #9 ; For put record
0604 9D4203 0320 STA ICCOM,X ; Command byte
0607 A91F 0330 LDA #MSG&255 ; Low byte of MSG
0609 9D4403 0340 STA ICBAL,X ; into ICBAL
060C A906 0350 LDA #MSG/256 ; High byte of MSG
060E 9D4503 0360 STA ICBAH,X ; into ICBAH
0611 A900 0370 LDA #0 ; Length of MSG
0613 9D4903 0380 STA ICBLH,X ; high byte
0616 A9FF 0390 LDA #$FF ; Length of MSG
0618 9D4803 0400 STA ICBLL,X ; See discussion
0410 ; *****************************
0420 ; Now put it to the screen
0430 ; *****************************
061B 2056E4 0440 JSR CIOV
061E 60 0450 RTS
0460 ; *****************************
0470 ; The message itself
0480 ; *****************************
061F 41 0490 MSG .BYTE "A SUCCESSFUL WRITE!",$9B
0620 20
0621 53
0622 55
0623 43
0624 43
0625 45
0626 53
0627 53
0628 46
0629 55
062A 4C
062B 20
062C 57
062D 52
062E 49
062F 54
0630 45
0631 21
0632 9B
0100 ; *****************************
0110 ; CIO equates
0120 ; *****************************
0340 0130 ICHID = $0340
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0343 0160 ICSTA = $0343
0344 0170 ICBAL = $0344
0345 0180 ICBAH = $0345
0346 0190 ICPTL = $0346
0347 0200 ICPTH = $0347
0348 0210 ICBLL = $0348
0349 0220 ICBLH = $0349
034A 0230 ICAXl = $034A
034B 0240 ICAX2 = $034B
E456 0250 CIOV = $E456
0000 0260 *= $600
0270 ; *****************************
0280 ; Now we load in required data
0290 ; *****************************
0600 A200 0300 LDX #0 ; Since it's IOCB0
0602 A909 0310 LDA #9 ; For put record
0604 9D4203 0320 STA ICCOM,X ; Command byte
0607 A91F 0330 LDA #MSG&255 ; Low byte of MSG
0609 9D4403 0340 STA ICBAL,X ; into ICBAL
060C A906 0350 LDA #MSG/256 ; High byte of MSG
060E 9D4503 0360 STA ICBAH,X ; into ICBAH
0611 A900 0370 LDA #0 ; Length of MSG
0613 9D4903 0380 STA ICBLH,X ; high byte
0616 A9FF 0390 LDA #$FF ; Length of MSG
0618 9D4803 0400 STA ICBLL,X ; See discussion
0410 ; *****************************
0420 ; Now put it to the screen
0430 ; *****************************
061B 2056E4 0440 JSR CIOV
061E 60 0450 RTS
0460 ; *****************************
0470 ; The message itself
0480 ; *****************************
061F 41 0490 MSG .BYTE "A SUCCESSFUL WRITE!",$9B
0620 20
0621 53
0622 55
0623 43
0624 43
0625 45
0626 53
0627 53
0628 46
0629 55
062A 4C
062B 20
062C 57
062D 52
062E 49
062F 54
0630 45
0631 21
0632 9B
Of course, writing to the screen is so simple in BASIC that there would be no reason to write this program as a subroutine for BASIC, so it doesn't contain the usual PLA instruction. Since you will need to print to the screen to debug assembly language programs, this routine may become one of your most frequently used programs.
In order to test this program once you have entered it, simply type ASM to assemble it, and when the assembly is complete, type BUG to enter the DEBUG mode of the Assembler/Editor cartridge. Then type G600 to begin execution at address $600. If the program has been typed correctly, the phrase A SUCCESSFUL WRITE! should appear, followed by the printing to the screen of the 6502 registers. These are printed following every routine that uses the cartridge. This same procedure should be used to test each of the routines given in this book. If problems arise, check your typing.
PLEASE
NOTE!!! SAVE YOUR PROGRAMS BEFORE
YOU TRY TO RUN THEM!!! Then if
they fail, or you have a computer crash, you won't have to retype the
entire program
In this program, we write the entire message to the screen by using the put-record command, storing a 9 into ICCOM. The address of the message we want to display on the screen is then stored into ICBLL and ICBLH, as before. We store a zero into the high byte of the length of the message, but $FF into the low byte.
Why $FF when the message is only 20 bytes long? When CIO is used in the put-record mode, the record is output byte by byte, until either the length of the buffer, set in ICBLL and ICBLH, has been exceeded or until a RETURN is encountered in the record being output. Note that the message which was set up in line 490 terminates with a byte of $9B, which is a RETURN. Therefore, the message will be sent to the screen, then a carriage return will be sent to the screen, and then the routine will terminate. We set the length of the record intentionally longer than the real message, because we want the $9B in the message itself to terminate the output. This way, we can't make a mistake and unintentionally cut the message short by setting ICBLL or ICBLH smaller than we intended.
It is important to note that we didn't set all of the bytes in the IOCB. In fact, we only needed to set the bytes that our particular routine used. As you'll see below, this is always the case with the central ATARI routines. The actual output to the screen is accomplished by the call to the central I/O routine in line 440, and the RTS in the next line returns control to the Assembler/Editor cartridge. If this were part of a larger assembly language program, the rest of the program would continue from line 450 without the RTS.
OTHER FORMS OF THE I/O ROUTINE
Let's look at another way to write a message to the screen, using the CIO system. Instead of loading ICCOM with 9, for put record, we can load it with 11, for put bytes. The other bytes of the IOCB are set as above, except for ICBLL, which is set to the exact length of the message. When counting the bytes in the message, don't forget to include the byte for the $9B, the RETURN. The program will then look like this:
Listing 9.3
0100 ; *****************************
0110 ; CIO equates
0120 ; *****************************
0340 0130 ICHID = $0340
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0343 0160 ICSTA = $0343
0344 0170 ICBAL = $0344
0345 0180 ICBAH = $0345
0346 0190 ICPTL = $0346
0347 0200 ICPTH = $0347
0348 0210 ICBLL = $0348
0349 0220 ICBLH = $0349
034A 0230 ICAX1 = $034A
034B 0240 ICAX2 = $034B
E456 0250 CIOV = $E456
0000 0260 *= $600
0270 ; *****************************
0280 ; Now we load in required data
0290 ; *****************************
0600 A200 0300 LDX #0 ; Since it's IOCB0
0602 A90B 0310 LDA #11 ; For put bytes
0604 9D4203 0320 STA ICCOM,X ; Command byte
0607 A91F 0330 LDA #MSG&255 ; Low byte of MSG
0609 9D4403 0340 STA ICBAL,X ; into ICBAL
060C A906 0350 LDA #MSG/256 ; High byte of MSG
060E 9D4503 0360 STA ICBAH,X ; into ICBAH
0611 A900 0370 LDA #0 ; Length of MSG
0613 9D4903 0380 STA ICBLH,X ; high byte
0616 A914 0390 LDA #20 ; Length of MSG
0618 9D4803 0400 STA ICBLL,X ; low byte
0410 ; *****************************
0420 ; Now put it to the screen
0430 ; *****************************
061B 2056E4 0440 JSR CIOV
061E 60 0450 RTS
0460 ; *****************************
0470 ; The message itself
0480 ; *****************************
061F 41 0490 MSG .BYTE "A SUCCESSFUL WRITE!",$9B
0620 20
0621 53
0622 55
0623 43
0624 43
0625 45
0626 53
0627 53
0628 46
0629 55
062A 4C
062B 20
062C 57
062D 52
062E 49
062F 54
0630 45
0631 21
0632 9B
0100 ; *****************************
0110 ; CIO equates
0120 ; *****************************
0340 0130 ICHID = $0340
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0343 0160 ICSTA = $0343
0344 0170 ICBAL = $0344
0345 0180 ICBAH = $0345
0346 0190 ICPTL = $0346
0347 0200 ICPTH = $0347
0348 0210 ICBLL = $0348
0349 0220 ICBLH = $0349
034A 0230 ICAX1 = $034A
034B 0240 ICAX2 = $034B
E456 0250 CIOV = $E456
0000 0260 *= $600
0270 ; *****************************
0280 ; Now we load in required data
0290 ; *****************************
0600 A200 0300 LDX #0 ; Since it's IOCB0
0602 A90B 0310 LDA #11 ; For put bytes
0604 9D4203 0320 STA ICCOM,X ; Command byte
0607 A91F 0330 LDA #MSG&255 ; Low byte of MSG
0609 9D4403 0340 STA ICBAL,X ; into ICBAL
060C A906 0350 LDA #MSG/256 ; High byte of MSG
060E 9D4503 0360 STA ICBAH,X ; into ICBAH
0611 A900 0370 LDA #0 ; Length of MSG
0613 9D4903 0380 STA ICBLH,X ; high byte
0616 A914 0390 LDA #20 ; Length of MSG
0618 9D4803 0400 STA ICBLL,X ; low byte
0410 ; *****************************
0420 ; Now put it to the screen
0430 ; *****************************
061B 2056E4 0440 JSR CIOV
061E 60 0450 RTS
0460 ; *****************************
0470 ; The message itself
0480 ; *****************************
061F 41 0490 MSG .BYTE "A SUCCESSFUL WRITE!",$9B
0620 20
0621 53
0622 55
0623 43
0624 43
0625 45
0626 53
0627 53
0628 46
0629 55
062A 4C
062B 20
062C 57
062D 52
062E 49
062F 54
0630 45
0631 21
0632 9B
This program accomplishes exactly the same end that the previous routine does, but in a different way. Both of these programs write a message to the screen that ends in a carriage return. There is a special case of writing to the screen in which we do not want the text to be followed by a return, such as when we are prompting for input, or when we would like to format the screen in a particular way. In BASIC, this instruction simply is a PRINT statement followed by a semicolon, which inhibits the normal carriage return following a PRINT command. If, for instance, we want to print a > symbol to the screen to prompt the user for input, but we want the cursor to remain on the same line as the symbol, in BASIC we could write the following line to accomplish this task:
PRINT
">";
In assembly language programming, the code is as follows:
Listing 9.4
0100 ; ******************************
0110 ; CIO equates
0120 ; ******************************
0340 0130 ICHID = $0340
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0343 0160 ICSTA = $0343
0344 0170 ICBAL = $0344
0345 0180 ICBAH = $0345
0346 0190 ICPTL = $0346
0347 0200 ICPTH = $0347
0348 0210 ICBLL = $0348
0349 0220 ICBLH = $0349
034A 0230 ICAX1 = $034A
034B 0240 ICAX2 = $034B
E456 0250 CIOV = $E456
0000 0260 *= $600
0270 ; *****************************
0280 ; Now we load in required data
0290 ; for special 1-character case
0300 ; *****************************
0600 A200 0310 LDX #0 ; Since it's IOCB0
0602 A90B 0320 LDA #11 ; For put bytes
0604 9D4203 0330 STA ICCOM,X ; Command byte
0607 A900 0340 LDA #0 ; Length of MSG
0609 9D4903 0350 STA ICBLH,X ; high byte
060C A900 0360 LDA #0 ; Length of MSG
060E 9D4803 0370 STA ICBLL,X ; low byte
0380 ; *****************************
0390 ; Now put it to the screen
0400 ; *****************************
0611 A93E 0410 LDA #62 ; For > prompt
0613 2056E4 0420 JSR CIOV
0616 60 0430 RTS
0100 ; ******************************
0110 ; CIO equates
0120 ; ******************************
0340 0130 ICHID = $0340
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0343 0160 ICSTA = $0343
0344 0170 ICBAL = $0344
0345 0180 ICBAH = $0345
0346 0190 ICPTL = $0346
0347 0200 ICPTH = $0347
0348 0210 ICBLL = $0348
0349 0220 ICBLH = $0349
034A 0230 ICAX1 = $034A
034B 0240 ICAX2 = $034B
E456 0250 CIOV = $E456
0000 0260 *= $600
0270 ; *****************************
0280 ; Now we load in required data
0290 ; for special 1-character case
0300 ; *****************************
0600 A200 0310 LDX #0 ; Since it's IOCB0
0602 A90B 0320 LDA #11 ; For put bytes
0604 9D4203 0330 STA ICCOM,X ; Command byte
0607 A900 0340 LDA #0 ; Length of MSG
0609 9D4903 0350 STA ICBLH,X ; high byte
060C A900 0360 LDA #0 ; Length of MSG
060E 9D4803 0370 STA ICBLL,X ; low byte
0380 ; *****************************
0390 ; Now put it to the screen
0400 ; *****************************
0611 A93E 0410 LDA #62 ; For > prompt
0613 2056E4 0420 JSR CIOV
0616 60 0430 RTS
If we set the length of the buffer equal to zero (by setting both the high and low bytes, ICBLL and ICBLH, to zero), then the character contained in the accumulator when CIOV is accessed will be printed to the output device without a following carriage return. This applies to all devices, including disk drives, tape recorders, printers and the screen, and it points out a very important feature of the ATARI computers: input and output are largely device-independent. That is, the OS treats all devices similarly, so we don't have to learn how to write a message to the screen, then learn a different way to send information to the printer, and learn still another method for passing information to the disk drive. The method is identical, once the IOCB has been opened for the device. To demonstrate this, we'll look at a routine to send the same message to a printer.
OUTPUT TO A PRINTER
First we'll close IOCB2, just to be on the safe side; then we'll open the printer as a device using IOCB2; and then we'll send our message.
Listing 9.5
0100 ; *****************************
0110 ; CIO equates
0120 ; *****************************
0340 0130 ICHID = $0340
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0343 0160 ICSTA = $0343
0344 0170 ICBAL = $0344
0345 0180 ICBAH = $0345
0346 0190 ICPTL = $0346
0347 0200 ICPTH = $0347
0348 0210 ICBLL = $0348
0349 0220 ICBLH = $0349
034A 0230 ICAX1 = $034A
034B 0240 ICAX2 = $034B
E456 0250 CIOV = $E456
0000 0260 *= $600
0270 ; *****************************
0280 ; First, close and open IOCB2
0290 ; *****************************
0600 A220 0300 LDX #$20 ; For IOCB2
0602 A90C 0310 LDA #12 ; Close command
0604 9D4203 0320 STA ICCOM,X ; Into ICCOM
0607 2056E4 0330 JSR CIOV ; Do the CLOSE
060A A220 0340 LDX #$20 ; IOCB2 again
060C A903 0350 LDA #3 ; Open file
060E 9D4203 0360 STA ICCOM,X ; Is the command
0611 A908 0370 LDA #8 ; Output
0613 9D4A03 0380 STA ICAX1,X ; Open for output
0616 A94C 0390 LDA #NAM&255 ; Low byte of device
0618 9D4403 0400 STA ICBAL,X ; Points to "P:"
061B A906 0410 LDA #NAM/256 ; High byte
061D 9D4503 0420 STA ICBAH,X
0620 A900 0430 LDA #0
0622 9D4903 0440 STA ICBLH,X ; High byte length
0625 A9FF 0450 LDA #$FF
0627 9D4803 0460 STA ICBLL,X ; Low byte length
062A 2056E4 0470 JSR CIOV ; Do the OPEN
0480 ; *****************************
0490 ; Now we'll print the message
0500 ; *****************************
062D A220 0510 LDX #$20 ; By using IOCB2
062F A909 0520 LDA #9 ; Put record
0631 9D4203 0530 STA ICCOM,X ; Command
0634 A94F 0540 LDA #MSG&255 ; Address of MSG
0636 9D4403 0550 STA ICBAL,X ; Low byte
0639 A906 0560 LDA #MSG/256 ; Address of MSG
063B 9D4503 0570 STA ICBAH,X ; High byte
063E A900 0580 LDA #0 ; Length of MSG
0640 9D4903 0590 STA ICBLH,X ; High byte
0643 A9FF 0600 LDA #$FF ; Length of MSG
0645 9D4803 0610 STA ICBLL,X ; Low byte
0648 2056E4 0620 JSR CIOV ; Put out the line
064B 60 0630 RTS ; End of routine
064C 50 0640 NAM .BYTE "P:",$9B
064D 3A
064E 9B
064F 41 0650 MSG .BYTE "A SUCCESSFUL WRITE!",$9B
0650 20
0651 53
0652 55
0653 43
0654 43
0655 45
0656 53
0657 53
0658 46
0659 55
065A 4C
065B 20
065C 57
065D 52
065E 49
065F 54
0660 45
0661 21
0662 9B
0100 ; *****************************
0110 ; CIO equates
0120 ; *****************************
0340 0130 ICHID = $0340
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0343 0160 ICSTA = $0343
0344 0170 ICBAL = $0344
0345 0180 ICBAH = $0345
0346 0190 ICPTL = $0346
0347 0200 ICPTH = $0347
0348 0210 ICBLL = $0348
0349 0220 ICBLH = $0349
034A 0230 ICAX1 = $034A
034B 0240 ICAX2 = $034B
E456 0250 CIOV = $E456
0000 0260 *= $600
0270 ; *****************************
0280 ; First, close and open IOCB2
0290 ; *****************************
0600 A220 0300 LDX #$20 ; For IOCB2
0602 A90C 0310 LDA #12 ; Close command
0604 9D4203 0320 STA ICCOM,X ; Into ICCOM
0607 2056E4 0330 JSR CIOV ; Do the CLOSE
060A A220 0340 LDX #$20 ; IOCB2 again
060C A903 0350 LDA #3 ; Open file
060E 9D4203 0360 STA ICCOM,X ; Is the command
0611 A908 0370 LDA #8 ; Output
0613 9D4A03 0380 STA ICAX1,X ; Open for output
0616 A94C 0390 LDA #NAM&255 ; Low byte of device
0618 9D4403 0400 STA ICBAL,X ; Points to "P:"
061B A906 0410 LDA #NAM/256 ; High byte
061D 9D4503 0420 STA ICBAH,X
0620 A900 0430 LDA #0
0622 9D4903 0440 STA ICBLH,X ; High byte length
0625 A9FF 0450 LDA #$FF
0627 9D4803 0460 STA ICBLL,X ; Low byte length
062A 2056E4 0470 JSR CIOV ; Do the OPEN
0480 ; *****************************
0490 ; Now we'll print the message
0500 ; *****************************
062D A220 0510 LDX #$20 ; By using IOCB2
062F A909 0520 LDA #9 ; Put record
0631 9D4203 0530 STA ICCOM,X ; Command
0634 A94F 0540 LDA #MSG&255 ; Address of MSG
0636 9D4403 0550 STA ICBAL,X ; Low byte
0639 A906 0560 LDA #MSG/256 ; Address of MSG
063B 9D4503 0570 STA ICBAH,X ; High byte
063E A900 0580 LDA #0 ; Length of MSG
0640 9D4903 0590 STA ICBLH,X ; High byte
0643 A9FF 0600 LDA #$FF ; Length of MSG
0645 9D4803 0610 STA ICBLL,X ; Low byte
0648 2056E4 0620 JSR CIOV ; Put out the line
064B 60 0630 RTS ; End of routine
064C 50 0640 NAM .BYTE "P:",$9B
064D 3A
064E 9B
064F 41 0650 MSG .BYTE "A SUCCESSFUL WRITE!",$9B
0650 20
0651 53
0652 55
0653 43
0654 43
0655 45
0656 53
0657 53
0658 46
0659 55
065A 4C
065B 20
065C 57
065D 52
065E 49
065F 54
0660 45
0661 21
0662 9B
Of course, if you are using an ATARI printer and want to print in expanded print, you'll have to set ICAX1 and ICAX2 before the final call to CIOV, but that's trivial. Note that we have not CLOSEd IOCB2 following the printing of our message, so if we want to print anything else, we can simply send it through IOCB2 without needing to OPEN it again. Of course, that also means that now we can't use IOCB2 for anything else, such as disk access. If we need to access the disk, we can use one of the other IOCBs, or we can CLOSE IOCB2 first and then reOPEN it for our disk operation.
Note that if we want the printhead to stop after printing a single character, without a trailing carriage return, we can use the special case of zero-length buffer, exactly as we did above for the screen.
OUTPUT TO A DISK
In order to show the versatility of the ATARI central I/O routines, we won't even give the program here to write the same line to a disk file. The method will be described, and you'll be able to send information to your disk on the first try, all by yourself! The only change needed in the program given above for the printer is this: to use the disk drive, the NAMe of the device is the disk file you wish to OPEN. Therefore, the program is identical to that given above, but line 640 should read something like:
640
NAM .BYTE "D1:MYFILE.1",$9B
That's all there is to it. You can see the beauty of using identical CIO routines for all devices, and you should now be able to output information to any device of your choosing in assembly language.
INPUT USING CIOV
The method for input from a device to your ATARI computer is exactly the same as that for output, but the device must be OPENed for input. We could, for instance, retrieve the above message from our disk file "D1:MYFILE.1" by OPENing this file for input, using a 3 in ICCOM and a 4 in ICAX1, and pointing ICBAL and ICBAH to the location in memory to which we want the message transferred. For instance, if we want the message to begin at memory location $680, we would set ICBAL to #$80 and ICBAH to #6; after the call to CIOV, memory locations $680 through $694 will contain the bytes of the message, which can then be examined by the remainder of our program.
The ease and simplicity of this I/O on an ATARI computer should not be underestimated. Learning each device is a separate chore with many other microcomputers; input and output may use different routines, each with their own peculiarities. The central I/O philosophy used in the ATARI greatly simplifies this process for us. Now you can use this system to greatly enhance your assembly language programming abilities.
One final note on the I/O routines: if we OPENed one IOCB for input from a file on the disk drive and a second IOCB for output to a printer or to the screen, it would be an trivial task to transfer information very quickly from one device to another by pointing to the same buffer for both IOCBs. Printing hard copy from and copying memory to a disk file is simple. We can even transfer information from the disk drive to the screen, or to a tape recorder.
NON-CIO INPUT AND OUTPUT
THREE DIFFERENT I/O SYSTEMS
Besides CIO, there are two other methods for using the disk drive as an input-output device; both reside in the OS. They use vectors called DSKINV and SIOV, at $E453 and $E459, respectively.
The three methods of disk I/O can be viewed as an onion, with multiple layers of control. The outer layer, which does most of the work for you, is the CIO system; the middle layer, which does some of the work for you, is the DSKINV system; and the inner layer, in which the programmer does all of the work, is the SIOV system. In fact, SIOV, the serial input-output vector, is used for all communications which take place over the serial bus, the 13-pronged connector on the side of your ATARI computer. Even the CIO system performs the actual input-output operations by calling SIO, after using the information in the IOCB to set up everything for SIO. DSKINV, about which we will learn more shortly, also calls SIO to perform the actual I/O.
DISK FILE TYPES
A floppy disk for your ATARI disk drive contains 40 concentric tracks, somewhat like a phonograph record. On a record, however, the tracks are actually one continuous spiral, whereas on a floppy disk, each track is a separate circle. Each track is divided into 18 sectors. To envision this, imagine cutting the disk like a pizza, with 18 equal slices. Then cut the pizza into 40 concentric circles, like a bullseye with 40 different colored rings. Each piece of the pizza is one sector. We'll have 18 X 40, or 720 sectors. On each of the sectors, the ATARI can store 128 bytes of information.
In the process of formatting a disk, not only are the 720 sectors created, but a Volume Table Of Contents (VTOC) and a disk directory are also created. The disk directory acts just like the table of contents of a book, listing each file (chapter) contained on the disk and the sector number where that file can be found (page). The VTOC keeps track of which sectors have already been filled and which remain empty, so that when we save a new file onto a partially full disk, we won't write over information already stored in another file. When we delete a file, its sectors are freed in the VTOC so that they can be used again. Note that when a file is deleted, only 1 byte of the file is actually changed – the status, or flag, byte. The first five bytes of the disk directory entry for each file are:
1. The status byte, which contains the status of the file. Each of 4 bits of the status byte are used to store specific information about that file:
Bit 0 set if file is open for output
Bit 5 set if file is locked
Bit 6 set if file is in use
Bit 7 set if file was deleted
Bit 5 set if file is locked
Bit 6 set if file is in use
Bit 7 set if file was deleted
2,3. The length of the file, in sectors, in the usual low byte-high byte order.
4,5. The number of the first sector of the file in low-high order.
If you have any of the many disk utility programs available, you can actually retrieve a deleted file, simply by changing the status byte from $80 to $40. However, if you write any information to the disk prior to trying this procedure, it will not work. The VTOC is changed when a file is deleted, freeing the sectors for use; if you've written to the disk, you'll find that some of the sectors previously used for the file you want to retrieve have been overwritten by the new information.
For the purpose of this discussion, we'll describe two different file types used by the ATARI computers. The first, and by far the most common, is the linked file, such as that created by this BASIC instruction:
SAVE
"D:GAME"
First, the disk directory is searched. Since a maximum of 64 files can be contained on a single disk, this check ensures that there is room in the disk directory for another file, called GAME. If, when checking the directory, a file called GAME is encountered, it is deleted (unless it is locked) and the new file replaces the old one. Assuming that this is the first GAME file to be SAVEd and that there is room in the disk directory, the first 125 bytes of the new file GAME are written to the first sector which the VTOC says is available. Note that only 125 bytes of the file are written, even though each sector can hold 128 bytes. This leaves room for the 3 bytes added by CIO, which lead to the name linked file for this type of file.
These 3 bytes contain the following information:
Byte Number
125 126 127
765432 10 76543210 7 6543210
file # forward link S byte ct
125 126 127
765432 10 76543210 7 6543210
file # forward link S byte ct
The high 6 bits of byte 125 are the file number, taken from the number of the file in the disk directory. For instance, if GAME is the fourth file listed in the directory, then the file number contained in the high 6 bits of byte 125 of every sector of GAME is 3, since the numbering starts with file 0. This number is checked when reading this file to ensure that each sector really belongs to the file GAME. If, when reading a file, a sector is encountered with a different file number, an error message will be displayed on your screen. This usually means that things have really been messed up on your disk; trying to fix such a file is a major undertaking.
The low order 2 bits of byte 125 are combined with byte 126 to produce a 10-bit number containing the number of the next sector of the file. Therefore, after the first sector is read, the next sector to be read can be determined from this forward link, and so on, until the whole file is read. That is why we call this a linked file. The last sector of each file contains 00 as a forward link, so we can determine when the entire file has been read.
Byte 127 of each sector of a linked file contains the number of bytes stored into that sector. In every sector except the last of each file, this will equal 125. If fewer than 125 bytes are contained in the sector, the high bit of byte 127, the S bit, will be set, denoting a Short sector of less than 125 bytes.
The second major type of disk file is called the sequential file, and is much simpler in structure. It uses neither the disk directory nor the VTOC, and uses all 128 bytes of each sector for storage. The sectors are read from such a file sequentially: sector 3 is read after sector 2, which was read after sector 1. The first sector of such a file contains the load address (where in memory to load this file) and the start address (where to begin execution of the program once the load is complete). This type of file is usually found on commercially available games. If you attempt to look at the disk directory of such a disk, you'll see only garbage, since no directory was ever set up for that disk.
USE OF THE DIFFERENT I/O SYSTEMS
CIO is generally used to read a linked file, such as a BASIC program, or, for that matter, the source code for an assembly language program. However, when you want to read a specific sector from the disk, generally DSKINV or SIO is used. Let's examine how we would accomplish these tasks using the three different types of I/O calls.
First, we'll open a disk file and read it into memory. The segment of the program that opens a file is very similar to the program above which opened the disk directory:
Listing 9.6
0100 ; *****************************
0110 ; CIO equates
0120 ; *****************************
0340 0130 ICHID = $0340
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0343 0160 ICSTA = $0343
0344 0170 ICBAL = $0344
0345 0180 ICBAH = $0345
0346 0190 ICPTL = $0346
0347 0200 ICPTH = $0347
0348 0210 ICBLL = $0348
0349 0220 ICBLH = $0349
034A 0230 ICAX1 = $034A
0348 0240 ICAX2 = $034B
E456 0250 CIOV = $E456
0000 0260 *= $600
0270 ; *****************************
0280 ; Open a file called OBJECT.COD
0290 ; *****************************
0600 A220 0300 LDX #$20 ; Use IOCB2
0602 A90C 0310 LDA #12 ; To close IOCB
0604 9D4203 0320 STA ICCOM,X ; Command byte
0607 2056E4 0330 JSR CIOV ; Do the close
0340 ; *****************************
060A A220 0350 LDX #$20 ; Use IOCB2 again
060C A903 0360 LDA #3 ; Open command
060E 9D4203 0370 STA ICCOM,X ; Command byte
0611 A904 0380 LDA #4 ; Open for read
0613 9D4A03 0390 STA ICAX1,X ; Into ICAX1
0616 A900 0400 LDA #0 ; 0 into ICAX2 is
0618 9D4B03 0410 STA ICAX2,X ; Just for insurance
061B A94F 0420 LDA #NAME&255 ; Low byte of file
061D 9D4403 0430 STA ICBAL,X ; Name address
0620 A906 0440 LDA #NAME/256 ; High byte - file
0622 9D4503 0450 STA ICBAH,X ; Name address
0625 2056E4 0460 JSR CIOV ; Open the file
0470 ; *****************************
0628 A220 0480 LDX #$20 ; IOCB2
062A A900 0490 LDA #0
062C 9D4403 0500 STA ICBAL,X ; Low byte-address
062F A950 0510 LDA #$50 ; High byte-address
0631 9D4503 0520 STA ICBAH,X ; Is then $5000
0634 A9FF 0530 LDA #$FF ; Make buffer length
0636 9D4803 0540 STA ICBLL,X ; Very long so the
0639 9D4903 0550 STA ICBLH,X ; Whole file loads
063C A905 0560 LDA #5 ; Get record
063E 9D4203 0570 STA ICCOM,X ; Command byte
0641 2056E4 0580 JSR CIOV ; Read the whole file
0590 ; *****************************
0644 A220 0600 LDX #$20 ; IOCB2
0646 A90C 0610 LDA #12 ; To close IOCB
0648 9D4203 0620 STA ICCOM,X ; Command byte
064B 2056E4 0630 JSR CIOV ; Do the close
064E 60 0640 RTS ; End of the routine
0650 ; *****************************
064F 44 0660 NAME .BYTE "D1:OBJECT.COD",$9B
0650 31
0651 3A
0652 4F
0653 42
0654 4A
0655 45
0656 43
0657 54
0658 2E
0659 43
065A 4F
065B 44
065C 9B
0100 ; *****************************
0110 ; CIO equates
0120 ; *****************************
0340 0130 ICHID = $0340
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0343 0160 ICSTA = $0343
0344 0170 ICBAL = $0344
0345 0180 ICBAH = $0345
0346 0190 ICPTL = $0346
0347 0200 ICPTH = $0347
0348 0210 ICBLL = $0348
0349 0220 ICBLH = $0349
034A 0230 ICAX1 = $034A
0348 0240 ICAX2 = $034B
E456 0250 CIOV = $E456
0000 0260 *= $600
0270 ; *****************************
0280 ; Open a file called OBJECT.COD
0290 ; *****************************
0600 A220 0300 LDX #$20 ; Use IOCB2
0602 A90C 0310 LDA #12 ; To close IOCB
0604 9D4203 0320 STA ICCOM,X ; Command byte
0607 2056E4 0330 JSR CIOV ; Do the close
0340 ; *****************************
060A A220 0350 LDX #$20 ; Use IOCB2 again
060C A903 0360 LDA #3 ; Open command
060E 9D4203 0370 STA ICCOM,X ; Command byte
0611 A904 0380 LDA #4 ; Open for read
0613 9D4A03 0390 STA ICAX1,X ; Into ICAX1
0616 A900 0400 LDA #0 ; 0 into ICAX2 is
0618 9D4B03 0410 STA ICAX2,X ; Just for insurance
061B A94F 0420 LDA #NAME&255 ; Low byte of file
061D 9D4403 0430 STA ICBAL,X ; Name address
0620 A906 0440 LDA #NAME/256 ; High byte - file
0622 9D4503 0450 STA ICBAH,X ; Name address
0625 2056E4 0460 JSR CIOV ; Open the file
0470 ; *****************************
0628 A220 0480 LDX #$20 ; IOCB2
062A A900 0490 LDA #0
062C 9D4403 0500 STA ICBAL,X ; Low byte-address
062F A950 0510 LDA #$50 ; High byte-address
0631 9D4503 0520 STA ICBAH,X ; Is then $5000
0634 A9FF 0530 LDA #$FF ; Make buffer length
0636 9D4803 0540 STA ICBLL,X ; Very long so the
0639 9D4903 0550 STA ICBLH,X ; Whole file loads
063C A905 0560 LDA #5 ; Get record
063E 9D4203 0570 STA ICCOM,X ; Command byte
0641 2056E4 0580 JSR CIOV ; Read the whole file
0590 ; *****************************
0644 A220 0600 LDX #$20 ; IOCB2
0646 A90C 0610 LDA #12 ; To close IOCB
0648 9D4203 0620 STA ICCOM,X ; Command byte
064B 2056E4 0630 JSR CIOV ; Do the close
064E 60 0640 RTS ; End of the routine
0650 ; *****************************
064F 44 0660 NAME .BYTE "D1:OBJECT.COD",$9B
0650 31
0651 3A
0652 4F
0653 42
0654 4A
0655 45
0656 43
0657 54
0658 2E
0659 43
065A 4F
065B 44
065C 9B
This program makes use of a trick to load the entire file in one operation. In lines 530 to 550, we set the length of the buffer to $FFFF, or 65,535 bytes. The CIO routine then will load the entire file, stopping either when 65,535 bytes have been loaded (an impossibility) or when an end-of-line byte is encountered. Therefore, if our file contains any end-of-line bytes ($9B), the load will terminate, and we won't load the entire file. How can we get around this problem?
Since we probably won't know for sure whether the file to be loaded will contain any $9B bytes, we should play it safe and use a method which will load any file. To do this, we load one sector (128 bytes) at a time, continuing until an error condition is achieved, which will occur at the end of the file. Using CIO, we know when an error occurs, since we will return from the call to CIO with the negative flag set. Let's take a look at the program to perform this type of load using CIO:
Listing 9.7
0100 ; *****************************
0110 ; CIO equates
0120 ; *****************************
0340 0130 ICHID = $0340
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0343 0160 ICSTA = $0343
0344 0170 ICBAL = $0344
0345 0180 ICBAH = $0345
0346 0190 ICPTL = $0346
0347 0200 ICPTH = $0347
0348 0210 ICBLL = $0348
0349 0220 ICBLH = $0349
034A 0230 ICAX1 = $034A
034B 0240 ICAX2 = $034B
E456 0250 CIOV = $E456
0000 0260 *= $600
0270 ; *****************************
0280 ; Open a file called OBJECT.COD
0290 ; *****************************
0600 A220 0300 LDX #$20 ; Use IOCB2
0602 A90C 0310 LDA #12 ; To close IOCB
0604 9D4203 0320 STA ICCOM,X ; Command byte
0607 2056E4 0330 JSR CIOV ; Do the close
0340 ; *****************************
060A A220 0350 LDX #$20 ; Use IOCB2 again
060C A903 0360 LDA #3 ; Open command
060E 9D4203 0370 STA ICCOM,X ; Command byte
0611 A904 0380 LDA #4 ; Open for read
0613 9D4A03 0390 STA ICAX1,X ; Into ICAX1
0616 A900 0400 LDA #0 ; 0 into ICAX2 is
0618 9D4B03 0410 STA ICAX2,X ; Just for insurance
061B A969 0420 LDA #NAME&255 ; Low byte of file
061D 9D4403 0430 STA ICBAL,X ; Name address
0620 A906 0440 LDA #NAME/256 ; High byte - file
0622 9D4503 0450 STA ICBAH,X ; Name address
0625 2056E4 0460 JSR CIOV ; Open the file
0470 ; *****************************
0628 A220 0480 LDX #$20 ; IOCB2
062A A900 0490 LDA #0
062C 9D4803 0500 STA ICBLL,X ; Low buffer length
062F A980 0510 LDA #$80 ; To load one sector
0631 9D4903 0520 STA ICBLH,X ; At a time
0634 A950 0530 LDA #$50 ; High byte of
0636 9D4503 0540 STA ICBAH,X ; Buffer address
0639 A905 0550 LDA #5 ; Get record
063B 9D4203 0560 STA ICCOM,X ; Command byte
063E A220 0570 LOOP LDX #$20 ; For when looping
0640 A900 0580 LDA #0 ; Low byte of buffer
0642 9D4403 0590 STA ICBAL,X ; Address @ start
0645 2056E4 0600 JSR CIOV ; Read 1st sector
0648 3014 0610 BMI FIN ; If done, go to FIN
064A A220 0620 LDX #$20 ; IOCB2
064C A980 0630 LDA #$80 ; Move up 128 bytes
064E 9D4403 0640 STA ICBAL,X ; For buffer
0651 2056E4 0650 JSR CIOV ; Read next sector
0654 3008 0660 BMI FIN ; If done, go to FIN
0656 A220 0670 LDX #$20 ; IOCB2
0658 FE4503 0680 INC ICBAH,X ; Raise buffer again
065B 4C3E06 0690 JMP LOOP ; Not done - read more
0700 ; *****************************
065E A220 0710 FIN LDX #$20 ; IOCB2
0660 A90C 0720 LDA #12 ; To close IOCB
0662 9D4203 0730 STA ICCOM,X ; Command byte
0665 2056E4 0740 JSR CIOV ; Do the close
0668 60 0750 RTS ; End of the routine
0760 ; *****************************
0669 44 0770 NAME .BYTE "D1:OBJECT.COD",$9B
066A 31
066B 3A
066C 4F
066D 42
066E 4A
066F 45
0670 43
0671 54
0672 2E
0673 43
0674 4F
0675 44
0676 9B
0100 ; *****************************
0110 ; CIO equates
0120 ; *****************************
0340 0130 ICHID = $0340
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0343 0160 ICSTA = $0343
0344 0170 ICBAL = $0344
0345 0180 ICBAH = $0345
0346 0190 ICPTL = $0346
0347 0200 ICPTH = $0347
0348 0210 ICBLL = $0348
0349 0220 ICBLH = $0349
034A 0230 ICAX1 = $034A
034B 0240 ICAX2 = $034B
E456 0250 CIOV = $E456
0000 0260 *= $600
0270 ; *****************************
0280 ; Open a file called OBJECT.COD
0290 ; *****************************
0600 A220 0300 LDX #$20 ; Use IOCB2
0602 A90C 0310 LDA #12 ; To close IOCB
0604 9D4203 0320 STA ICCOM,X ; Command byte
0607 2056E4 0330 JSR CIOV ; Do the close
0340 ; *****************************
060A A220 0350 LDX #$20 ; Use IOCB2 again
060C A903 0360 LDA #3 ; Open command
060E 9D4203 0370 STA ICCOM,X ; Command byte
0611 A904 0380 LDA #4 ; Open for read
0613 9D4A03 0390 STA ICAX1,X ; Into ICAX1
0616 A900 0400 LDA #0 ; 0 into ICAX2 is
0618 9D4B03 0410 STA ICAX2,X ; Just for insurance
061B A969 0420 LDA #NAME&255 ; Low byte of file
061D 9D4403 0430 STA ICBAL,X ; Name address
0620 A906 0440 LDA #NAME/256 ; High byte - file
0622 9D4503 0450 STA ICBAH,X ; Name address
0625 2056E4 0460 JSR CIOV ; Open the file
0470 ; *****************************
0628 A220 0480 LDX #$20 ; IOCB2
062A A900 0490 LDA #0
062C 9D4803 0500 STA ICBLL,X ; Low buffer length
062F A980 0510 LDA #$80 ; To load one sector
0631 9D4903 0520 STA ICBLH,X ; At a time
0634 A950 0530 LDA #$50 ; High byte of
0636 9D4503 0540 STA ICBAH,X ; Buffer address
0639 A905 0550 LDA #5 ; Get record
063B 9D4203 0560 STA ICCOM,X ; Command byte
063E A220 0570 LOOP LDX #$20 ; For when looping
0640 A900 0580 LDA #0 ; Low byte of buffer
0642 9D4403 0590 STA ICBAL,X ; Address @ start
0645 2056E4 0600 JSR CIOV ; Read 1st sector
0648 3014 0610 BMI FIN ; If done, go to FIN
064A A220 0620 LDX #$20 ; IOCB2
064C A980 0630 LDA #$80 ; Move up 128 bytes
064E 9D4403 0640 STA ICBAL,X ; For buffer
0651 2056E4 0650 JSR CIOV ; Read next sector
0654 3008 0660 BMI FIN ; If done, go to FIN
0656 A220 0670 LDX #$20 ; IOCB2
0658 FE4503 0680 INC ICBAH,X ; Raise buffer again
065B 4C3E06 0690 JMP LOOP ; Not done - read more
0700 ; *****************************
065E A220 0710 FIN LDX #$20 ; IOCB2
0660 A90C 0720 LDA #12 ; To close IOCB
0662 9D4203 0730 STA ICCOM,X ; Command byte
0665 2056E4 0740 JSR CIOV ; Do the close
0668 60 0750 RTS ; End of the routine
0760 ; *****************************
0669 44 0770 NAME .BYTE "D1:OBJECT.COD",$9B
066A 31
066B 3A
066C 4F
066D 42
066E 4A
066F 45
0670 43
0671 54
0672 2E
0673 43
0674 4F
0675 44
0676 9B
We continue to loop until we encounter an error on I/O, at which time we branch to FIN to close the file and finish the routine. You must be careful that no errors other than the end-of-file error occur, since this program will branch to FIN on any error. It would, of course, be fairly easy to write a routine to first determine the error code returned in the Y register after the call to CIOV and then take appropriate action depending on the error code. Note that in this routine, we have to take care of some of the housekeeping for loading the file, such as incrementing the buffer address in lines 630, 640, and 680; we didn't have to worry about this in the first example. We also have to build in routines that we didn't formerly need to determine when we are done loading.
LOADING USING THE RESIDENT DISK HANDLER
In order to utilize the resident disk handler, the programmer must set up a Device Control Block (DCB), which is exactly analogous to the IOCB we need to set up when we use CIO. The equates for this DCB are as follows:
0100
; *****************************
0110 ; SIO equates
0120 ; *****************************
0130 DDEVIC = $0300 ; Serial bus I.D.
0140 DUNIT = $0301 ; Device number
0150 DCOMND = $0302 ; Command byte
0160 DSTATS = $0303 ; Status byte
0170 DBUFLO = $0304 ; Low buffer address
0180 DBUFHI = $0305 ; High buffer address
0190 DTIMLO = $0306 ; Disk timeout
0210 DBYTLO = $0308 ; Low byte count
0220 DBYTHI = $0309 ; High byte count
0230 DAUX1 = $030A ; Auxiliary #1
0240 DAUX2 = $030B ; Auxiliary #2
0250 SIOV = $E459
0260 DSKINV = $E453
0110 ; SIO equates
0120 ; *****************************
0130 DDEVIC = $0300 ; Serial bus I.D.
0140 DUNIT = $0301 ; Device number
0150 DCOMND = $0302 ; Command byte
0160 DSTATS = $0303 ; Status byte
0170 DBUFLO = $0304 ; Low buffer address
0180 DBUFHI = $0305 ; High buffer address
0190 DTIMLO = $0306 ; Disk timeout
0210 DBYTLO = $0308 ; Low byte count
0220 DBYTHI = $0309 ; High byte count
0230 DAUX1 = $030A ; Auxiliary #1
0240 DAUX2 = $030B ; Auxiliary #2
0250 SIOV = $E459
0260 DSKINV = $E453
The third byte of both the IOCB and DCB is the command byte, although the command bytes themselves are different in the two systems, and the fifth and sixth bytes of both systems are the buffer address. Only the following 5 command bytes are allowed by the resident disk handler:
$21 format a disk
$50 write a sector
$52 read a sector
$53 status request
$57 write a sector with write-verify
$50 write a sector
$52 read a sector
$53 status request
$57 write a sector with write-verify
It is therefore apparent that the resident disk handler is a more limited but a far simpler system than CIO. Let's look at how we can use the DCB and the resident disk handler, through DSKINV, to read information from the disk. Of course, we will not be reading regular DOS files using this system; they are linked files, and the resident disk handler is not designed to handle linked files, but rather sequential ones. Let's therefore assume that we want to read sectors $20 through $60, inclusive, rather than some disk file. The program to do this using DSKINV follows:
Listing 9.8
0100 ; *****************************
0110 ; SIO equates
0120 ; *****************************
0300 0130 DDEVIC = $0300 ; Serial bus I.D.
0301 0140 DUNIT = $0301 ; Device number
0302 0150 DCOMND = $0302 ; Command byte
0303 0160 DSTATS = $0303 ; Status byte
0304 0170 DBUFLO = $0304 ; Low buffer address
0305 0180 DBUFHI = $0305 ; High buffer address
0306 0190 DTIMLO = $0306 ; Disk timeout
0308 0200 DBYTLO = $0308 ; Low byte count
0309 0210 DBYTHI = $0309 ; High byte count
030A 0220 DAUX1 = $030A ; Auxiliary #1
030B 0230 DAUX2 = $030B ; Auxiliary #2
E459 0240 SIOV = $E459
E453 0250 DSKINV = $E453
0000 0260 *= $600
0270 ; *****************************
0280 ; Assume file begins at sector
0290 ; $20 and extends to sector $60
0300 ; *****************************
0600 A900 0310 LDA #0
0602 8D0B03 0320 STA DAUX2 ; High sector number
0605 8D0803 0330 STA DBYTLO ; Low buffer length
0608 A980 0340 LDA #$80 ; To load one sector
060A 8D0903 0350 STA DBYTHI ; At a time
060D A950 0360 LDA #$50 ; High byte of
060F 8D0503 0370 STA DBUFHI ; Buffer address
0612 A952 0380 LDA #$52 ; Get sector
0614 8D0203 0390 STA DCOMND ; Command byte
0617 A920 0400 LDA #$20 ; Low sector number
0619 8D0A03 0410 STA DAUX1 ; Goes here
061C A900 0420 LOOP LDA #0 ; Low byte of buffer
061E 8D0403 0430 STA DBUFLO ; Address @ start
0621 2053E4 0440 JSR DSKINV ; Read 1st sector
0624 A980 0450 LDA #$80 ; Move up 128 bytes
0626 8D0403 0460 STA DBUFLO ; For buffer
0629 EE0A03 0470 INC DAUX1 ; Next sector
062C AD0A03 0480 LDA DAUX1 ; Are we done?
062F C960 0490 CMP #$60
0631 B010 0500 BCS FIN ; Yes
0633 2053E4 0510 JSR DSKINV ; No - read next sector
0636 EE0503 0520 INC DBUFHI ; Raise buffer page
0639 EE0A03 0530 INC DAUX1 ; Next sector
063C AD0A03 0540 LDA DAUX1 ; Are we done?
063F C960 0550 CMP #$60
0641 90D9 0560 BCC LOOP ; No
0643 60 0570 FIN RTS ; All finished
0100 ; *****************************
0110 ; SIO equates
0120 ; *****************************
0300 0130 DDEVIC = $0300 ; Serial bus I.D.
0301 0140 DUNIT = $0301 ; Device number
0302 0150 DCOMND = $0302 ; Command byte
0303 0160 DSTATS = $0303 ; Status byte
0304 0170 DBUFLO = $0304 ; Low buffer address
0305 0180 DBUFHI = $0305 ; High buffer address
0306 0190 DTIMLO = $0306 ; Disk timeout
0308 0200 DBYTLO = $0308 ; Low byte count
0309 0210 DBYTHI = $0309 ; High byte count
030A 0220 DAUX1 = $030A ; Auxiliary #1
030B 0230 DAUX2 = $030B ; Auxiliary #2
E459 0240 SIOV = $E459
E453 0250 DSKINV = $E453
0000 0260 *= $600
0270 ; *****************************
0280 ; Assume file begins at sector
0290 ; $20 and extends to sector $60
0300 ; *****************************
0600 A900 0310 LDA #0
0602 8D0B03 0320 STA DAUX2 ; High sector number
0605 8D0803 0330 STA DBYTLO ; Low buffer length
0608 A980 0340 LDA #$80 ; To load one sector
060A 8D0903 0350 STA DBYTHI ; At a time
060D A950 0360 LDA #$50 ; High byte of
060F 8D0503 0370 STA DBUFHI ; Buffer address
0612 A952 0380 LDA #$52 ; Get sector
0614 8D0203 0390 STA DCOMND ; Command byte
0617 A920 0400 LDA #$20 ; Low sector number
0619 8D0A03 0410 STA DAUX1 ; Goes here
061C A900 0420 LOOP LDA #0 ; Low byte of buffer
061E 8D0403 0430 STA DBUFLO ; Address @ start
0621 2053E4 0440 JSR DSKINV ; Read 1st sector
0624 A980 0450 LDA #$80 ; Move up 128 bytes
0626 8D0403 0460 STA DBUFLO ; For buffer
0629 EE0A03 0470 INC DAUX1 ; Next sector
062C AD0A03 0480 LDA DAUX1 ; Are we done?
062F C960 0490 CMP #$60
0631 B010 0500 BCS FIN ; Yes
0633 2053E4 0510 JSR DSKINV ; No - read next sector
0636 EE0503 0520 INC DBUFHI ; Raise buffer page
0639 EE0A03 0530 INC DAUX1 ; Next sector
063C AD0A03 0540 LDA DAUX1 ; Are we done?
063F C960 0550 CMP #$60
0641 90D9 0560 BCC LOOP ; No
0643 60 0570 FIN RTS ; All finished
As we saw above, the further we get from the initial CIO routine, the more housekeeping we must take care of. In this program, we must handle the incrementing of the disk sectors and the buffer location after each read, and we must determine whether we are done by constantly comparing the sector number to the final sector desired, $60. This is what we meant when we compared the various I/O systems to the layers of an onion. The closer we get to the core, the more work we have to do, and the less the system handles for us.
At the very core is the Serial Input-Output system (SIO) itself. We accessed DSKINV in this program, but we could have called SIOV instead. However, before doing so, we would have had to set up the entire DCB instead of just the pertinent bytes, as we did. For instance, the serial bus ID would have had to be set to $31, in DDEVIC, and the timeout value to some reasonable value, like 45. Then we could have accomplished exactly the same results by replacing each call to DSKINV with a call to SIOV, but with the expense of still more housekeeping.
Note that both CIOV and DSKINV themselves call SIOV to actually accomplish the serial input and output, but they handle their respective housekeeping tasks before these calls. The further you get from CIO, the more precise your control of the system, but the more work for yourself. This is a general rule in computing – a high level language is the easiest to use, but gives you the least control of the system. As you gain more control, you also need to work harder. Well, you really didn't expect to get something for nothing, did you?
This concludes our discussion of disk I/O. You should now be completely familiar with how to get information to and from a disk drive, either using sequential or linked files. Experiment with these systems until you feel comfortable, since they are basic to many applications that you will want to try.
Return to Table of Contents | Previous Chapter | Next Chapter