ALL ABOUT I/O
Important tutorial for both assembly language and BASIC
by MARK ANDREWS
A complete tutorial explaining how to print to the screen in assembly language. Includes valuable information on the I/O system for all programmers, but assumes some beginning knowledge of machine language. Two demonstration listings are included which will run on all Atari computers but require either Atari Assembler Editor or Macl65 (OSS). Antic disk subscribers should follow the procedure outlined in the article. Both object and source files are on the disks.
If you're an advanced beginner or intermediate Atari programmer, you're
aware that the techniques for controlling Input and Output (I/O) activities
are not always easy to figure out. In fact, this topic brings in
questions from Antic readers month after month. But now, every answer
you need for taking full charge of your I/O destiny is right here ... in
Antic's second excerpt from Mark Andrews' outstanding book Atari
Roots - A Guide to Atari Assembly Language.
If you read last issue's excerpt, "First Lesson in Assembly Language", you know why we say that Atari Roots is the clearest-written and most understandable book for learning Atari assembly language that we've come across yet. But this chapter, "All About I/O", is just as useful for a BASIC programmer as for an assembly language student because much of the material is closely related in both languages.
Atari Roots ($14.95) is published by Datamost, 20660 Nordhoff St., Chatsworth, CA 91311. (818) 709-1202.
Types of I/O Devices
Many kinds of I/O devices can be connected to your Atari computer. But there are seven specific kinds of devices that can be addressed in both Atari BASIC and Atari assembly language using specific procedures and specific commands. Each of these seven types of devices has a unique one letter abbreviation, or device name, by which it can be addressed in both Atari BASIC and Atari assembly languages. These seven types of devices, and their corresponding device names in both BASIC and assembly language are:
- Keyboard (K:).
- Line Printer (P:).
- Program (Cassette) Recorder (C:).
- Disk Drives (D:) (or, if more than one disk drive is used, Dl:, D2:, D3:, and D4:).
- Screen Editor (E:).
- TV Monitor (Screen) (S:).
- RS-232 Serial Interface (R:).
Note the colon following the letter in each of these abbreviations. The colon is an integral part of each device name, and may not be omitted.
The Eight Atari I/O Operations
In both Atari BASIC and Atari assembly language, there are eight I/O operations that can be performed using the seven abbreviations, or device names, listed above. These eight I/O operations are:
- OPEN (to open a specified device).
- CLOSE (to close a specified device).
- GET CHARACTER (to read one character from a specified device or file).
- PUT CHARACTER (to write one character to a specified device or file).
- READ RECORD (to read the next record, a string which must end with a return character [$9B] from a specified device or file).
- WRITE RECORD (to write a record, a string, which must end with a return character [$9B] to a specified device or file).
- STATUS (to get the status of a specified device).
- SPECIAL (to perform a specified special operation on specified device used primarily in file management and RS-232 serial operations).
In both Atari BASIC and Atari assembly language, all of the I/O operations listed earlier are designed to be performed using a centralized peripheral interface system called the Central I/O Utility, or CIO. The Atari CIO system, like most peripheral interface systems, is designed to handle sequences of data bytes called files. A file may contain data, text, or both, and it may or may not be arranged by records, strings of text or data separated by end of line characters (ATASCII code $9B). Some files, such as files recorded on disks, can be given individual names (such as "Dl.TESTIT.SRC). Other files, such as those used with the Atari screen editor or line printer, do not have individual names, but are addressed simply by the name of the device on which they appear, for example, "E:" or "P:"
Both Atari BASIC and Atari assembly language allow programmers to access up to eight different devices and/or files at the same time. In both BASIC and assembly language, this access is provided via eight dedicated blocks of memory that are called Input/Output Control Blocks, or IOCBS. In Atari Assembly language, just as in Atari BASIC, the eight IOCBs are numbered from 0 to 7. In both assembly language and BASIC, any free IOCB number can be assigned to any I/O device, although IOCB #0 is always assigned to the screen editor when an Atari computer is first turned on, and is the screen editor's default IOCB number.
Opening a Device
In both Atari BASIC and Atari assembly language, I/O devices are assigned IOCB numbers when they are first addressed, or opened. When a device is first opened for either read or write operations, an IOCB number must be assigned to it. Once an IOCB number has been assigned to a device, the device can be referred to by that number until a command to close the device is issued. Once a device is closed, the IOCB number that was assigned to it becomes free again, and can be used to open any other device in your computer system.
Assembly Language Lacks IOCB Commands
In Atari BASIC, specific commands are provided to open, close, read from and write to any I/O devices that may be connected to a computer. No such commands exist in 6502 assembly language. The IOCB system used in Atari computers does provide the assembly language programmer with a means of handling all of the I/O devices that can be connected to an Atari computer. It can handle it in a way that is relatively easy to manage and easy to understand.
Opening a Device Using Atari BASIC
It is not difficult to open a device or a file using Atari BASIC. To open a device or a file, all a BASIC programmer has to do is write a line using the following formula.
10 OPEN #n,nl,n2,filespec
The following is an example of an Atari BASIC statement written using the standard IOCB formula.
10 OPEN #2,8,0:"Dl:TESTIT.BAS"
As you can see, there are five components in an OPEN statement in Atari BASIC: The OPEN command itself, a series of three parameters separated by commas, and a device name plus a file name, if applicable. A mandatory "#" mark appears before the first parameter after the OPEN statement and the device name is followed by a mandatory colon. In addition, the device name and the file name, if applicable, are enclosed in mandatory quotation marks. The meanings of the five components of an OPEN statement are explained below.
1. "OPEN" - the OPEN command.
2. "#n" (#2 in the sample statement above)-The IOCB number. This number, as we have pointed out, ranges from 0 through 7. "#2" in this position means "IOCB #2."
3. "n1" (8 in our example)-A code number for a specific type of input or output operation. In our sample OPEN statement, the "8" in this position is the code number for an output (open for write) operation.
4. "n2" (O in our sample statement)-A device dependent auxiliary code sometimes used for various purposes (in this case, though, not used).
5. "filespec"-A device name plus a file name, if applicable. In our example, "Dl.TESTIT.BAS" refers to a file called TESTIT.BAS which our computer will expect to find stored on a disk in disk drive 1.
How BASIC Processes an "OPEN" Command
When your computer encounters an OPEN command while processing a BASIC program, it carries out a series of standardized operations using the values in each of the four parameters of the OPEN statement. When all of those operations are completed, BASIC jumps to a special OS subroutine called the CIO vector, or CIOV The CIOV subroutine then automatically opens the device in question, referring to the parameters that were contained in the OPEN statement (and are now stored in certain memory locations) in order to make sure that the proper device is opened for the kind of access called for in the OPEN statement.
Advantages of Assembly Language I/O Operations
To understand how a device is opened using Atari assembly language, it's helpful to know how devices are opened using Atari BASIC. That's because BASIC programs and assembly language programs open devices in exactly the same way. The only difference is that when you open a device using BASIC, your BASIC interpreter does most of the work for you. When you use assembly language, you have to do all of the work yourself. Fortunately, there's a payoff for doing all of this extra work. When you control your system's CIO system using assembly language, you have a lot more control over the system than you do when you allow BASIC to do all the work.
Opening a Device Using Assembly Language
Now let's take a look at exactly how devices are opened, read from, written to and closed, in both Atari BASIC and Atari assembly language.
Another Look at IOCBs
As we've pointed out, the I/O operations of an Atari computer are controlled using a series of eight I/O control blocks, or IOCBS. Each of these I/O control blocks is an actual block of memory in your computer. Each IOCB is 16 bytes long, and each byte in each IOCB has a specific name and a specific function. Moreover, each byte in each IOCB has the same name, and performs the same kind of function, as the corresponding byte in every other IOCB. That's important, so let's say it again in a different way: Each byte in each IOCB in your computer has the same name, and performs the same kind of function, as the byte with the same offset in each other IOCB.
Indirect Addressing in IOCB Operations
The reason this fact is important is that indirect addressing is used quite often in IOCB operations. Indirect addressing is a technique in which a memory location is sought out by means of an offset value stored in the 6502 processor's X or Y register. Since the offsets of all of the bytes in all Atari IOCBs correspond to each other, that makes the indirect addressing mode very easy to use in Atari IOCB operations.
The 16 Bytes of an IOCB
This concept is much easier to understand when examples are given. So an actual assembly language program will be used to explain the Atari I/O system. It shows how to print messages on the screen.
Listing 1 is the program which we will examine. Listing 2 is a brief routine which uses listing 1 to print a short message to the screen. To use the two listings, type them in using either Atari Assembler Editor or Mac/65. Save the source code to disk: listing 1 will be PRNTSC.ASM and listing 2 PRTSMPLE.ASM. Next, assemble the two source files into compiled object files called PRNTSC.OBJ and PRTSMPLE.OBJ.. (See your assembler for proper procedure.) Atari DOS 2.0 users should load both files into memory by typing L and then the filename for each file. After the files are in memory, type M and respond to the address prompt with 066A. DOSXL users should LOAD each file then type RUN 066A.
"PRNTSC.ASM," Line by Line
Now we'll take a good close look at this program and see how it works, line by line. We'll start with the first three lines of the program, lines 290 through 310.
Initializing a Device for "OPEN"
300 LDX #IOCB2
310 LDA #OPEN
320 STA ICCOM,X
Substitute literal numbers for the variables in these three lines, and this is how they will read.
300 LDX #$20
310 LDA #$03
320 STA $342,X
These instructions are all it takes to open a device in Atari assembly language. To understand what they do, you have to know something about the structure of an Atari IOCB. As we've pointed out, there are eight IOCBs in your Atari's operating system, and each one contains 16 bytes (or $10 bytes in hexadecimal notation). That means that to address IOCB #1, you have to add 16 (or $10) bytes to the address of IOCB #0 and to address IOCB #2, you have to add 32 (or $20) bytes to the address of IOCB #0. In other words, when you use the address of IOCB #0 as a reference point (as the Atari CIO system does), the offset you have to use is 32 in decimal notation, or $20 using the hexadecimal system. Here are all of the IOCB offsets used, in the Atari CIO system:
The Eight Atari IOCB Offsets
Now let's take another look at our literal value version of the first three lines of the PRNTSC.SRC program:
300 LDX #$20
310 LDA #$03
320 STA $342,X
Now you can begin to see why the number $20 has been loaded into the X register in line 300. Obviously, it's going to be used as an offset in line 320, but before we move on to line 320, let's take a look at line 310, the line in between. In line 310, the accumulator is loaded with the number $03-which has been identified back in line 110 of the program as the "token for opening a device." Now what does that mean?
Well, in the Atari CIO system, each of the eight I/O operations described at the beginning of this chapter can be identified by a one-digit (hex) code, or token. Here is a complete fist of those tokens, and the operations for which they stand.
Token Name Function
$03 OPEN Open a specified
device or file.
$04 OREAD Open a device or file for read operations.
$08 OWRITE Open a device or file for write operations.
$05 GETREC Read a record from a specified device or file.
$07 GETCHR Read character from specified device or file.
$09 PUTREC Write a record to a specified device or file.
$OB PUTCHR Writ character from specified device or file.
$OC CLOSE Close a specified device or file.
Line 310 Explained
Now you can see what happens in line 310 of the program PRNTSC.ASM. The accumulator is loaded with the number $03, the token for "OPEN". In line 320, the OPEN token is stored in the indirect acddress ICCOM,X (or $342,X). Just what is this address?
ICCOM is the name of one of the 16 bytes in an IOCB. Specifically, ICCOM is the first byte (the zero offset byte) in every IOCB. Look at line 170 of the PRNTSC.ASM program and you'll see that ICCOM is located at memory address $342, and is identified as the "command byte" in the Atari CIO system. It is called the command byte because it is the byte that must be addressed when devices are to be initialized, opened or closed. ICCOM is the byte that points to a set of subroutines in your computer's operating system that perform all of those functions.
Since we have listed all of the Atari I/O devices, I/O commands, I/O offsets and I/O operation codes so far, we might as well provide a list of ICCOM and the rest of the 16 bytes in each of your computer's IOCBS. Here is a complete list of the bytes in each IOCB.
Byte Adrs Name Function
ICHID $0340 Handler I.D.
Preset by OS
ICDNO $0341 Device Number Preset by OS
ICCOM $0342 Command Byte Controls CIO operations
ICSTK $0343 Status Byte Returns status of operations
ICBAL $0344 Buffer Address, Low Holds address of text buffer
ICBAH $0345 Buffer Address, High Holds address of text buffer
ICFITL $0346 Unused Pointer Not used in programming
ICPTH $0347 Unused Pointer Not used in programming
ICBLL $0348 Buffer Length, Low Holds length of text buffer
ICBLH $0349 Buffer Length, High Holds length of text buffer
ICAXI $034A Auxiliary Byte No. 1 Picks write or read operation
ICAX2 $034B Auxiliary Byte No. 2 Used for various purposes
ICAX3 $034C Auxiliary Byte No. 3 Used by OS only
ICAX4 $034D Auxiliary Byte No. 4 Used by OS only
ICAX5 $034E Auxiliary Byte No. 5 Used by OS only
ICAX6 $034F Auxiliary Byte No. 6 Used by OS only
Now you can understand the operation performed in lines 300 through 320 of the PRNTSC.SRC program.
300 LDX #IOCB2
310 LDA #OPEN
320 STA ICCOM,X
In line 300, the X register is loaded with the offset for IOCB #2: the
number $20. In line 310, the accumulator is loaded with the token
for the OPEN operation: the number $03. In line 320, the token of
the OPEN operation (the number $03) is stored in ICCOM,X: the command byte
of IOCB #2. After a few more operations, we're going to issue a "JSR
CIOV" (jump to SubRoutine) statement, so our Atari will jump to the CIO
vector and open IOCB #2, as we have instructed. But first, we're
going to have to set a few more parameters, so our computer will know exactly
what kind of operations to open IOCB #2 for. So let's zip right through
the rest of this "OPEN" operation now.
In lines 340 through 370, the text buffer in IOCB #2 is loaded with the address of a variable defined in line 270 as DEVNAM. The variable DEVNAM, as you can see by looking at line 270 contains the ATASCII code for the character string "E: " the device name for the Atari screen editor. We could have opened IOCB #2 for any other I/O device in exactly the same way. If we wanted to use IOCB #2 as a printer IOCB, for example, we could have written line 270 this way:
Then in lines 340 through 370, the address of the ATASCII string "P:",EOL would be loaded in ICRAL,X. With that tiny change, the PRNTSC program, instead of opening your computer screen as an output device, would open your printer! You can also use this same programming procedure to open a specific file on a disk so that you can read from it or write to it, on either a character-by-character or a record-by-record basis. In the PRNTSC program, we could open a disk file instead of the screen editor by changing line 270 to read something like this:
Then, instead of opening the screen editor, our program would open the disk file TESTIT.BAS (provided, of course, that there was a disk drive connected to our computer and that all other necessary conditions for opening such a file existed). We have just seen two examples of the tremendous power of the Atari CIO system. While the system may seem complex at first glance, its incredible versatility is a real testament to the programming know-how of Atari's computer designers.
Let's continue on now with our "OPEN" operation. In lines 390 and 400, we load the number $08 the token for "open a device for a write operation" into Auxiliary Byte No. 1 of IOCB #2. We could make our program do something completely different if we stored the value $04, the token for "open read," in ICAXL,X instead of the value $08, the token for "open write" That's another demonstration of the versatility of the Atari CIO system.
We have now read lines 410 and 420, in which we clear Auxiliary Byte No. 2 of IOCB #2 (a byte that is not used in this routine) by stuffing it with a zero. Finally, in line 430, we jump to the Atari CIO vector at memory address $E456. With that operation, we have opened IOCB #2 for a write operation to the Atari screen editor. In other words, we have opened IOCB #2 to print on the screen.
Printing a Character
We have not yet actually printed a character on the screen, however. To do that, we must carry out two more sequences of I/O operations. Now that you understand how the Atari CIO system works, that will be a snap. Look at lines 450 through 610 of the PRNTSC.ASM program.
In lines 450 and 460, we store the number $0B, the token for a "put character" operation, into the command byte of IOCB #2. In lines 480 through 520, the address of the text buffer we have created especially for this program is stored in the buffer address bytes of IOCBC#2. That prepares us for the PRNT routine that starts at line 540. In the PRNT routine, which extends from line 540 to line 610, the length of our specially created text buffer is stored in the buffer length bytes of IOCB #2. Then there is another jump to the CIO vector, which automatically takes care of printing the text in the PRNTSC text buffer on your computer screen.
Closing a Device
When you open a device in assembly language (as in Atari BASIC), you must close it when you're finished with it. Otherwise, you'll cause an IOCB error, and that could cause some serious problems.
Forgetting to carry out such tasks as closing IOCBs (at the time they should be closed) can lead to program crashes and long and agonizing debugging sessions. Anyway, IOCB #2 is closed in this version of the PRNTSC program. in Lines 630 through 680, the value of $OC-the token for closing a file-is loaded into ICCOM,X. Then there's a jump to CIOV, and the Atari OS closes the IOCB.
Mark Andrews has written 11 books about computers and is a syndicated computer columnist He recently moved from Manhattan to San Francisco's Telegraph Hill.