Chapter Nine


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

    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

    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)

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

    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:
  1. 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.

  2. IOCB6, the screen display for graphics modes higher than zero. This IOCB is used for all graphics commands, like PLOT, DRAWTO, FILL, and others.

  3.  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.
    As you have probably already recognized, BASIC uses the IOCB numbers (0, 6, and 7) to direct output to these devices, as when printing to a GRAPHICS 1 or 2 screen with this command:

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

    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

    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

    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

    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

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

    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

    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

    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

    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

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

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

    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

    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

    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

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

    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