Index

COMPUTE! ISSUE 16 / SEPTEMBER 1981 / PAGE 70

INSIGHT: Atari

Bill Wilkinson
Cupertino, CA

Editor’s Note: We’re quite pleased to announce a new column this month for Atari owners. INSIGHT: Atari, written by Bill Wilkinson and other staff members of Optimized Systems Software, will bring you monthly programming insight and support.

We feel you’ll be quite pleased. —RCL

Hi. I’m Bill Wilkinson, and this is the premiere of what will be a regular feature in COMPUTE! magazine: a column dedicated to the software side of the Atari microcomputers. We may occasionally include little tricks to make better use of the hardware, but the intent is that this column will uncover the facts and foibles of Atari software.

This column will normally be written by some of the authors of Atari BASIC, Atari’s Assembler-Editor, Atari’s Disk File Manager, and BASIC A+ and OS/A+. We are not all experts in Atari hardware, but we know a lot about the software.

Addressable DATA or Who Needs String Arrays?

Perhaps the most frequent complaint made about Atari BASIC pertains to its lack of string arrays. In 10K bytes of ROM one can pack only so much program; long variable names and instant syntax checking take room; HP and DG have very successful BASICs that don’t use string arrays; Atari-style strings are fast and flexible. All this doesn’t mean much to you if you can’t figure a way to convert that neat Applesoft program to Atari. There are many legitimate uses of string arrays, but the most common use is a kind of in-memory random access data file. Example: in an adventure game the various room descriptions are kept in elements of a string array. This is not the fullest exploitation of string arrays, since the data is static and the arrays merely provide a convenient method of addressing it.

Atari BASIC users, take heart! You have available to you an even more powerful and flexible method of randomly addressing static data. Did you ever notice that Atari BASIC supports the syntax “RESTORE line-number”? Did you ever notice that “line-number” can be either a constant number or (surprise) any arbitrary numeric expression? These two capabilities combine to allow some extremely powerful programming constructs in Atari BASIC. The following short program will serve to illustrate.

1000 REM a demonstration of addressable DATA
1010 REM allocate some variables
1020 DIM ROOM$(100),GO$(1),DIRECTION(4),DIRECTION$(4)
1030 LET DIRECTION$="NESW"
1100 REM the following variables are used as line numbers, etc.
1110 LOOKROOM=3000:LOOP=2000:DESCRIPTIONS=9000
1120 DESCRIPTIONSIZE=10
1900 REM variables are set up - initialize player status
1910 ROOM=2:GOSUB LOOKROOM
2000 REM the main program loop
2010 PRINT "WHICH WAY";:INPUT GO$
2020 DIRECTION=0
2030 FOR I=1 TO 4:IF GO$=DIRECTION$(I,I) THEN DIRECTION=I
2040 NEXT I
2050 IF NOT DIRECTION THEN GOTO LOOP
2060 GO=DIRECTION(DIRECTION)
2070 IF NOT GO THEN PRINT "CAN'T GO THAT WAY":GOTO LOOP
2080 IF GO>1000 THEN GOSUB GO:GOTO LOOP
2090 ROOM=GO:GOSUB LOOKROOM
2100 GOTO LOOP
3000 REM subroutine to get and print details of a new room
3010 RESTORE DESCRIPTIONS+ROOM*DESCRIPTIONSIZE
3020 FOR I=1 TO 4:READ TEMP:DIRECTION(I)=TEMP:NEXT I
3030 READ ROOM$:PRINT "YOU ARE IN";ROOM$
3040 RETURN
8000 REM special routines for special actions
8010 PRINT "YOU MADE IT OUT! CONGRATULATIONS!":END
9000 REM the room descriptions and connections
9010 DATA 3,5,0,0,A LARGE CAVERN
9020 DATA 0,4,5,3,A SMALL CAVERN
9030 DATA 0,2,1,0,A CURVING PASSAGEWAY
9040 DATA 0,8010,5,2,AN ANTECHAMBER
9050 DATA 2,4,0,1,A MAZE OF TUNNELS

Let us go through this program carefully and search out the tricks. Lines 1000–1030 are fairly straight forward; the variable names were purposefully chosen to demonstrate that Atari BASIC considers all characters in a name to be significant. Lines 1100–1120 initialize the variables which will be used for “address arithmetic” later in the program; “LOOP,” for example, simply gives a name to the line number where all lite action starts.

Line 1910 begins the start of the tricks: it GOSUBs to LOOKROOM, Notice how much more readable this is than simply coding GOSUB 3000, which tells you nothitig of the purpose of the statement. Looking at routine LOOKROOM (lines 3000–3040), we note the usage of “RESTORE expression.” As an example, assume that LOOKROOM is called with ROOM=2. Then line 3010 becomes equivalent to “RESTORE 9020.” The subsequent READs then fill the array DESCRIPTION with the numeric data of line 9020 and the string ROOM$ with the string, “A SMALL CAVERN.” Finally, the user is prompted with a message (“YOU ARE IN A SMALL CAVERN.”) and the subroutine exits.

Continuing our main program at lines 2000–2040, we simply ask the user for a direction (from the choices ‘N’, ‘E’, ‘S’, and ‘W’). An invalid answer causes DIRECTION to be zero and the question to be asked again. Let us assume we are still in room two and also assume that the WHICH WAY? query was answered by “E.” GO then becomes 4 (from DIRECTION(2)); and, since it is nonzero (line 2070) and less than 1000 (line 2080), the current ROOM becomes number 4 and we GOSUB LOOKROOM again.

The only things left to note about this program are what happens if GO is zero (e.g., if we had tried to go “N” from room 2) or greater than 1000 (if we try to go “F.” from room A)? The case of GO=0 is easy: the program treat’s that as an illegal move, prints “CAN’T GO THAT WAY,” and makes the player try again. For GO greater than 1000, another action unique to Atari BASIC happens, we GOSUB to the apparent room number contained in GO. In the particular example shown, the only GOSUB is to line 8010 which ends the “adventure,” but this mechanism can be used to allow sophisticated checks on movement (e.g., you can only go from room 31 to room 33 if you have the Golden Fleece). The concept of addressable GOSUBs was heavily exploited, and we will try to cover those techniques in a future column.

Each of these columns will cover one or two programming topics and answer a few questions (presuming that you, the reader, will supply us with some questions). In this initial column, we would like to try to comment on some of the points raised in the “ASK THE READERS” column from COMPUTE! #14.


16K Memory

I. Regarding D. Gallagher’s query about PRINT FRE(0) in his 48K machine.

When you plug the first (left, in an Atari 800) cartridge into an Atari, you “lose” the top 8K of the possible 48K of RAM. Thus your 48K does you no more good than 40K would. It can get worse: if Atari ever comes out with a dual cartridge product, you will lose the top 16K of your 48K. The reason: Atari’s memory map simply doesn’t leave any other place to put the cartridges, so Atari cleverly arranged the circuitry so that plugging in the cartridge disables any RAM at the same addresses. Does this mean that it is a waste to put 48K bytes of RAM into your Atari? Not at all! There are several products already available that use no cartridges at all (Visicalc, BASIC A+, Forth, etc.). In fact, look for Atari systems with 160K bytes of RAM, or more, in the near future. And by the way, it is not surprising to hear of the “foreign” memory board in the Atari: systems suppliers have been doing that in the minicomputer (DEC, HP, etc.) and S-100 (8080 and Z-80) markets for years! After all, if the dealer can give you more for less, why complain? Oh yes, for the curious, herewith the Atari memory map:

┌────────────────────────────────┐ FFFF
│ OS ROM (10K bytes)             │
├────────────────────────────────┤ D800
│ I/O Hardware                   │
├────────────────────────────────┤ D000
│ Reserved for future use        │
├──────────┬─────────────────────┤ C000
│   48K    │    Left cartridge   │
│          ├─────────────────────┤ A000
│    of    │    Right cartridge  │
│          └─────────────────────┤ 8000
│   RAM                          │
│                                │
└────────────────────────────────┘ 0000

II. Comments about the letter discussing RFI from an APPLE II.

Atari owners, stand up and be proud! Did you know that your machine is the only full-fledged computer that was able to pass the FCC’s former (and very strict) RFI regulations? But thanks to TI, and some extensive lobbying with the FCC, the RFI rules are much relaxed and even the Apple II (with the help of some new shielding) can now pass the tests. But even so, the Atari has to be one of the quietest (in terms of RFI) machines ever produced. So while you owners are enjoying noise-free television, remember that the abysmally slow disk I/O speeds you also “enjoy” are part of Atari’s solution to the RFI problem. That serial bus didn’t just happen by accident; it was the result of some superb—but alas, no longer necessary—engineering.

III. An answer to Tracy Principio about GR.X from assembly language.

Anyone contemplating writing in assembly language for the Atari is virtually required to purchase the Hardware Manuals (as did Tracy); but, even if you don’t have a disk, the Atari DOS manuals and OS listings are also de rigueur. Any kind of I/O must go through CIO, the heart of the Atari OS, and graphics on the Atari are most easily done via I/O. Did you know that PLOT, DRAWTO, POSITION, FILL, and more are not in Atari Basic? They are actually routines in the I/O section of the OS ROM, and BASIC simply provides an interface to them. So, if you understand the I/O subsystem, you can do graphics in assembly language almost as easily as you can do them in BASIC. The whole subject of I/O and graphics from assembly language would make a beautiful series of columns (tell us if you’d like to see some), so we must “answer” Tracy’s question by noting that GR.X is equivalent to:

  OPEN #6,12,X-16,"S:" if X is greater than 16
    (full screen graphics)
  or OPEN #6,12+16,X,"S:" if X is less than 16
    (mixed characters and graphics).

Note that the “12” is simply 8+4, read and write access, just as with a disk.


That’s all for this month. We hope that by increasing your awareness of its capabilities we can convert you, too, into more informed and capable Atari users.

COMPUTE! ISSUE 17 / OCTOBER 1981 / PAGE 102

INSIGHT: Atari

Bill Wilkinson
Optimized Systems Software
Cupertino, CA

Last month we explored the possibilities inherent in the fact that Atari BASIC supports addressable DATA statements. This month we will tackle a related subject. It will be fairly obvious that the techniques presented in these two articles can help one write a fast yet complex adventure game on the Atari. However, it would be most interesting to me to see what other uses you readers make of these ideas, so start those cards and letters coming!

Nonexistent Subroutines… Or
Having Fun While GOSUBing Nowhere

Purists should probably not read this section: we will plumb depths that advocates of structured programming would never sink to. Just as Atari BASIC allows the code RESTORE <expression>, we can also use GOTO <expression> and GOSUB <expression>. (Don’t worry about the notation: <anything> just means that “anything” is an English language word instead of a reserved BASIC word.) Allowing GOTO and GOSUB to refer to arbitrary expressions instead of absolute line numbers is unusual in BASICs (and well nigh impossible in most other languages), so perhaps the power of this capability has never been fully realized. We will try to make a few inroads.

Before we get into the more exotic part of our discussion, let us note the most obvious advantage of “line number expressions”: self-documenting code. How much more meaningful it is to be able to code GOSUB CALCULATEPAY instead of GOSUB 13250!! Admittedly, with Atari BASIC one must first have written LET CALCULATEPAY=13250 ; but that is a small price to pay for the added readability. Fair warning: there is one drawback to this trick. Atari BASIC allows only 128 different variable names. Normally that is a very big number, but naming every subroutine or section of code can eat up variables in a hurry. Be judicious in your choice of which routines are worth naming.

Now it is time for our main topic. And, thus, time for an example program. Study the example carefully before continuing with this text.

Lines 1000–1030 are simply set-up and initialization, one time operations. Lines 2000–2260 constitute the main loop of the program (in fact, in this simplistic example, this is an endless loop). First, the user is asked for a verb. If the INPUT verb matches one in the DATA list, it is assigned an appropriate verb number. The process is repeated for a noun. In an actual adventure game, the user would presumably be asked for “VERB NOUN” via a single INPUT; the program would then have to parse the request into the separate words.

At line 2200–2260 we exhibit the trick that this article is all about. To understand what happens, let us follow through what would happen if the user had requested “KISS BABY.” “KISS” is verb number 2 and “BABY” is noun number 3, so at line 2220 we attempt to GOSUB to line 10000+2*1000+3*10; that is we attempt to GOSUB 12030. Lo and behold, line 12030 causes the message “YOU HAVE JUST BEEN ELECTED MAYOR” to print out. When the routine RETURNs, we GOTO LOOP and do this all over again.

But wait: suppose the user had typed “LOOK BABY.” That phrase evaluates to verb 1 and noun 3, so we try to GOSUB 11030 (10000+ 1*1000+3*10). But there is no line 11030! All is not lost: note that on line 2210 we first TRAPped to line 2250. The attempt to GOSUB 11030 will trigger the TRAP and we indeed will continue execution at line 2250. Here, we attempt to GOSUB to line 10000+1*1000, effectively ignoring the noun. We succeed in executing line 11000, the “default” routine for the verb “LOOK,” and find that the computer sees “NOTHING SPECIAL” about this baby.

The power implicit here is perhaps not obvious. But consider how easy it is to add new verbs and nouns to this program. Consider how easy it is to provide for as many or as few special verb-noun combinations as you wish. And, finally, look how little code is used!

Note that this program expects there will be a routine for each valid verb (it’s only sensible: why have a verb in the DATA if it doesn’t do anything)? Another TRAP statement, at line 2250, could allow for omitted verbs. By the way, with the program written as it is, there is no way to get to line 2250 with the error TRAP system still active. Atari BASIC always resets any TRAP when it is triggered (this is so that you can’t accidentally fall into endless TRAP loops).

The techniques discussed in this and prior articles have actually been used to write a “PICO-ADVENTURE.” The most amazing aspect of the program is the speed with which it responds: it seems as fast or faster than even machine language adventures. Try it. Let us know about your efforts.

Unreadable Programs

There follow two program lines that can be added to any existing Atari BASIC program and which, when executed, will make a program virtually unLISTable. The first line simply changes the names of all your variables to RETURN characters, and can be used with or without the second line. The second line actually produces a BASIC SAVEd program that can only be RUN—not LISTed, LOADed, etc.


Atari BASIC version:
32766 FOR I=PEEK(130)+256*PEEK(131) TO PEEK(132)+256*PEEK(133):POKE I,155:NEXT I
32767 POKE PEEK(138)+256*PEEK(139)+2,0:SAVE "<filename>":NEW
BASIC A+ version:
32766 for i=dpeek(130) to dpeek(132):poke i,155:next i
32767 poke dpeek(138)+2,0:save "<filename>":new

To use these gems, simply enter them and then type GOTO 32766. The line numbers are not important, but the second line must be the last line of the program. To use the resulting program, simply type RUN "<filename>". The program should not end with STOP or END; instead, it should exit via NEW (yes, “NEW” can be used from within a program). The <filename> may be “C:”, but CLOAD will not work (you must use RUN "C:").

VARIABLE, VARIBLE, VARABLE

Perhaps one of the more common mistakes when using the long variable names allowed by Atari BASIC is to make a typo when entering the name (I tend to leave off the plural, “s”). How to know you have committed this sin? Try the following program segment:


Using Atari BASIC:
32700 I=0:FOR J=PEEK(130)+256*PEEK(131) TO PEEK(132)+256*PEEK(133)-1
32710 IF PEEK(J)<128 THEN PRINT CHR$(PEEK(J));:GOTO 32730
32720 PRINT CHR$(PEEK(J)-128):I=I+1
32730 NEXT J:PRINT :PRINT I;" VARIABLES IN USE":STOP
Using BASIC A+:
32700 i=0:for j=dpeek(130) to dpeek(132)-1:print chr$(peek(j)&127);
32710 if peek(j)>128:print :i=i+1:endif:next j
32720 print: print i;" variables in use":stop

It would be advisable to LIST this program segment to DISK or CASSETTE and then ENTER it into any program that needs it. To use, simply type GOTO 32700 ; all your variables will be listed and a count displayed. Obviously, this output could be sent to the printer by first OPEN #7,8,0,"P:" and then replacing all the PRINTs with PRINT #7. If you do this, it is advisable to CLOSE #7 before the STOP.


1000 REM ***** SET UP ********
1010 DIM VERB$(4),NOUN$(4),TEST$(4)
1020 VERBDATA=9000:NOUNDATA=9100
1030 LOOP=2000
2000 REM ***** MAIN LOOP ******
2010 PRINT "GIVE ME A VERB ";:INPUT VERB$
2020 RESTORE VERBDATA:VERB=0
2030 FOR CNT=1 TO 3:REM CHANGE TO MATCH DATA
2040 READ TEST$
2050 IF TEST$=VERB$ THEN VERB=CNT:CNT=99
2060 NEXT CNT
2070 IF NOT VERB THEN PRINT "INVALID VERB":GOTO LOOP
2100 REM VERB DONE, DO NOUN
2110 PRINT "GIVE ME A NOUN ";:INPUT NOUN$
2120 RESTORE NOUNDATA:NOUN=0
2130 FOR CNT=1 TO 3:REM CHANGE TO MATCH DATA
2140 READ TEST$
2150 IF TEST$=NOUN$ THEN NOUN=CNT:CNT=99
2160 NEXT CNT
2170 IF NOT NOUN THEN PRINT "INVALID NOUN":GOTO LOOP
2200 REM ***** THE TRICKY STUFF *****
2210 TRAP 2250
2220 GOSUB 10000+VERB*1000+NOUN*10
2230 GOTO LOOP
2240 REM WE GET TO 2250 ONLY ON TRAP
2250 GOSUB 10000+VERB*1000
2260 GOTO LOOP
9000 REM A LIST OF ALL VERBS
9010 DATA LOOK,KISS,DROP
9100 REM A LIST OF ALL NOUNS
9110 DATA ROOM,BEAR,BABY
10000 REM ***************************
10010 REM *    THE VERB-NOUN ACTION *
10020 REM ***************************
11000 REM >>> LOOK <<<
11001 PRINT "I SEE NOTHING SPECIAL":RETURN
11010 PRINT "I SEE A WINDOW AND A DOOR":RETURN
12000 REM >>> KISS <<<
12001 PRINT "THAT'S SILLY...BUT SMACK":RETURN
12020 PRINT "BEAR BITES OFF YOUR LIPS":RETURN
12030 PRINT "YOU HAVE JUST BEEN ELECTED MAYOR":RETURN
13000 REM >>> DROP <<<
13001 PRINT "HOW? I COULDN'T HAVE LIFTED THAT.":RETURN
13030 PRINT "IT'S A BOUNCING BABY BOY!!":RETURN
COMPUTE! ISSUE 18 / NOVEMBER 1981 / PAGE 96

INSIGHT: Atari

Bill Wilkinson
Cupertino, CA

In my September column, I mentioned that the subject of graphics and I/O would make a nice series of columns. I wondered if there would be enough interest in the topic to justify the writing effort. Since that time, I have (in the course of talking to our customers) discovered that not only is there interest in the topic, but there is also a woeful lack of information and an abundance of misinformation regarding Atari’s OS. So, with this column, we start a three or four part series on assembly language I/O.

Also, this month’s column includes a list of major, known bugs in Atari BASIC and how to get around them.

Atari I/O, Part One: Interfacing To OS

Before I get started with the hairy details, I would like to state that Atari has the best operating system in the low-end microcomputer market. There is a simple reason for this: Atari has the only operating system on the market! Now, admittedly, I am being a purist when I make this contention, but the truth is that the Atari is the only machine I know of that has a true operating system in ROM. And, no, neither my company (Optimized Systems Software) nor I were involved in the creation of that operating system; the credit must go straight to Atari.

The operating system is contained in ROM and is identical on both the Atari 400 and Atari 800. The 10K bytes of ROM you may have noticed contain not only the operating system, but also the upper/lower case character set, the floating point mathematical operations, the power-on and cartridge select logic, and the device drivers. Device drivers? Aren’t those part of the operating system? No! An emphatic “no.” And that’s what enables me to say that Atari has the only true operating system.

Believe it or not, the operating system on the Atari occupies less than 700 bytes. And yet it is as complete in its own way as UNIX is on a large time sharing machine. How many times have you read a magazine and seen lists of addresses of I/O subroutines for XYZ computer? You must use this address to output a character to the screen, another to get a character from the keyboard, yet another to talk to the line printer, and disk I/O? A nightmare! Not so Atari. One and only one address need be remembered: Hex E456, Decimal 58454. (Yes, I know, why not E400 or F000 or some such. Well, I didn’t say Atari was perfect, only good.)

With only one address that matters, you can imagine that it should be easy for Atari to come out with new versions of the OS without affecting any other programs. They have, and they are, and only programs that have “cheated” (gone outside the OS rules) are in trouble. So, don’t get yourself in trouble; follow the OS rules.

Finally, to avoid duplication of effort, I would refer you to the massive program listing for “SHOOT” in COMPUTE! #16: the first two pages and first column of the next two pages constitute most of the useful equates when using the Atari from assembly language. We are concerned with the “Operating System Equates” (first column, second page) and “Page three RAM assignments” (third column, first page). I will use the mnemonics given in that listing throughout this series of articles. (Those of you who own our “OS/A+” system will find these equates, with some mnemonics altered slightly for consistency, in the file “SYSEQU.ASM”, You may use the EASMD pseudo-op “.INCLUDE #D:SYSEQU.ASM” to include them in any assembly programs. This will save you some typing.)

The Structure Of The IOCB’s

When a program calls the OS through location $E456, OS expects to be given the address of a properly formatted IOCB (Input Output Control Block). For simplicity. Atari has predefined eight IOCB’s, each 16 bytes long, and the program specifies which one to use by passing the IOCB number times 16 in the 6502’s X-register. Thus, to access IOCB number four, the X-register should contain $40 on entry to OS. Notice that the IOCB number corresponds directly to the file number in BASIC (as in PRINT #6, etc.). Actually, the IOCB’s are located from $0340 to $03BF (refer to the “SHOOT” listing).

When OS gets control, it uses the X-register to inspect the appropriate IOCB and determine just what it was that the user wanted done. Table 1 gives the Atari standard names for each field in the IOCB along with a short description of the purpose of the field. Study the Table before proceeding.

Table 1
FIELD
NAME
OFFSET
WITHIN
IOCB
(BYTES)
SIZE
OF
FIELD
(BYTES)


PURPOSE OF FIELD
ICHID 0 1 SET BY OS. Index into device name table for currently OPEN file, set to $FF if no file open on this IOCB.
ICDNO 1 1 SET BY OS. Device number (e.g., 1 for "D1:xxx" or 2 for "D2:yyy")
ICCOM 2 1 The COMMAND request from user program. Defines how rest of IOCB is formatted.
ICSTA 3 1 SET BY OS. Last status returned by device. Not necessarily the status returned via STATUS command request.
ICBAL
ICBAH
4 2 BUFFER ADDRESS. A two byte address in normal 6502 low/high order. Specifies address of buffer for data transfer or address of filename for OPEN, STATUS, etc.
ICPTL
ICPTH
6 2 SET BY OS. Address minus one of device’s put-one-byte routine. Possibly useful when high speed single byte transfers are needed.
ICBLL
ICBLH
8 2 BUFFER LENGTH. Specifies maximum number of bytes to transfer for PUT/GET operations. Note: this length is decremented by one for each byte transferred.
ICAX1 10 1 Auxiliary byte number one. Used in OPEN to specify kind of file access needed. Some drivers can make additional use of this byte.
ICAX2 11 1 Auxiliary byte number two. Some serial port functions may use this byte. This and all following AUX bytes are for special use by each device driver.
ICAX3
ICAX4
12 2 For disk files only: where the disk sector number is passed by NOTE and POINT. (These bytes could be used separately by other drivers.)
ICAX5 14 1 For disk files only: the byte-within-sector number passed by NOTE and POINT.
ICAX6 15 1 A spare auxiliary byte.

The user program should never touch fields ICHID, ICDNO, ICSTA and ICPTL/ICPTH. In addition, unless the particular device and I/O request requires it, the program should not change ICAX1 through ICAX6. The most important field is the one-byte command code, ICCOM, which tells the operating system what function is desired.

The OS itself only understands a few fundamental commands, but Atari wisely provided for extended commands necessary to some devices (XIO in BASIC). In any case, each one of these fundamental commands deserves a short description.

Table 2 provides the OS commands and their usage of the various fields of the IOCB’s. For convenience, the disk file manager extended commands are also shown, but I must withhold discussion of them until next month.

Table 2
IOCB field
offset
and
name
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
BUFFER
ADDRESS
PUT-A-BYTE
ADDRESS
BUFFER
LENGTH
Type of
command
ICHID
 
ICDNO
 
ICCOM
 
ICSTA
 
ICBAL
 
ICBAH
 
ICPTL
 
ICPTH
 
ICBLL
 
ICBLH
 
ICAX1
 
ICAX2
 
ICAX3
 
ICAX4
 
ICAX5
 
ICAX6
 
Mnemonic label used by Atari for this field
OPEN * * 3 * FILENAME * * SEE
TEXT
OPEN
CLOSE * 12 * CLOSE
DYNAMIC
STATUS
* 13 * FILENAME STATIS
GET TEXT
RECORD
5 * BUFFER LENGTH GETREC
PUT TEXT
RECORD
9 * BUFFER LENGTH PUTREC
GET BINARY
RECORD
7 * BUFFER LENGTH GETCHR
PUT BINARY
RECORD
11 * BUFFER LENGTH PUTCHR
EXTENDED COMMANDS: DISK FILE MANAGER ONLY
RENAME * 32 * FILENAME RENAME
ERASE * 33 * FILENAME DELETE
PROTECT * 35 * FILENAME LOCKFL
UNPROTECT * 36 * FILENAME UNLOCK
NOTE 38 * SECTOR
NUMBER
BYTE NOTE
POINT 37 * SECTOR
NUMBER
BYTE POINT
      —LEGEND—
* — Set by OS when this comand is used.
BUFFER — 16-bit address of a data buffer.
FILENAME — 16-bit address of a filename.
LENGTH — length (in byles) of a data buffer.
SECTOR NUMBER and BYTE — see text.

Device names on the Atari computers are very simplistic; they consist of a single letter (optionally followed by a single numeral). Traditionally (and, in the case of disk files, of necessity) the device name is followed by a colon. You have probably seen these device names in your various Atari manuals, but a quick summary might be convenient:

Other device names are possible (e.g., for RS-232 interfaces) and, in fact, the ease with which other devices may be added is another reason for my claim that Atari has a true operating system. The structure of device drivers is material for a later article, but I should like to point out that the OS ROM includes drivers for all the above except the disk. In fact, the drivers account for over 5K bytes of the ROM code. The screen handler, with all its associated editing and Graphics modes, occupies about 3K bytes of that.

Actually, the next column will begin to delve deeper into the ways of using OS, but for those of you anxious and brave enough to get started now we present a very simple example program:

PUTMSG                    ;A ROUTINE TO PRINT A MESSAGE
          LDX #$00        ;WE USE IOCB NUMBER 0, THE CONSOLE (E:)
          LDA #PUTREC
          STA ICCOM,X     ;THE COMMAND IS 'PUT TEXT RECORD'
          LDA #MSG&255
          STA ICBAL,X     ;LOWER BYTE OF ADDRESS OF 'MSG'
          LDA #MSG/256
          STA ICBAH,X     ;UPPER BYTE OF ADDRESS
          LDA #255
          STA ICBLL,X     ;LOWER BYTE OF LENGTH OF MSG
          STA ICBLH,X     ;UPPER BYTE, LENGTH IS ALL OF MEMORY
                          ;BUT 'PUTREC WILL STOP WITH THE 'RETURN' CHAR
          JSR CIOV        ;CALL THE OS TO DO THE WORK
          TYA             ;MOVES RETURNED ERROR CODE TO A-REGISTER
          BMI ERROR       ;ANY NEGATIVE VALUE IS SOME SORT OF ERROR
          ...
          ...             ;CONTINUE WITH MORE CODE
          ...
MSG       .BYTE 'THIS IS A MESSAGE',$9B

Just a very few notes on this routine: (1) If the command had been “GETREC,” the OS would have gotten a line from the keyboard and put it into the “buffer” at MSG. (2) If the X-register had been set to $20 and if the printer had previously been OPENed at IOCB number 2, then this same code would have sent the message to the printer. (3) If the buffer length had been given as less than 18, the message would have been truncated to the specified length. That’s all on I/O for this month. I hope you will hound your mailbox until your next issue of COMPUTE! arrives.

Bugs In BASIC

Several people have requested a list of all known bugs in Atari BASIC. The following list may not be complete, but it certainly enumerates all the bugs that may be considered “killers.”

  1. In the course of editing a BASIC program, sometimes the system loses all or part of the program and/or simply hangs. Often, turning power off and back on is the only solution. Contrary to popular belief, this condition is related to nothing except the size of the program that is being moved by a delete operation (not the size of the deleted line). FIX: NONE. Sorry about that. Just be sure and SAVE your programs often, especially if you are doing heavy editing.
  2. String assignments that involve the movement of multiples of 256 bytes do not move the first 256 bytes. FIX: don’t move multiples of 256 bytes. An easy way to accomplish this is to always move an ODD number of bytes. Usually, moving one extra byte is fairly easy to handle.
  3. The cassette handler doesn’t always properly initialize its hardware interface. Symptoms: ERROR 138 and ERROR 143. FIX: use an LPRINT before doing a CSAVE, etc. (This isn’t a BASIC bug, but BASIC can be used to fix it.)
  4. Taking the unary minus of a zero number (e.g., PRINT -0) can result in garbage. Usually this garbage will not affect subsequent calculations, but it does print strangely. FIX: don’t use the unary minus in cases where there may be a doubt (e.g., use PRINT 0-x if ‘x’ might be zero).
  5. Strange things can happen if you type in a program line longer than three screen lines long. Reason: the system editor device (E:) cuts off your input at three lines and gives it to BASIC, which processes it as is, and then E: gives the rest of your input to BASIC as the next line! FIX: don’t try to put in program lines bigger than three screen lines.
  6. Using an INPUT statement without a variable (i.e., just ‘10 INPUT’) does not cause a syntax error (it should) and may cause program lock-up when RUN. FIX: don’t do it. (What did you expect? BASIC is in ROM, so it can’t be fixed.)
  7. Most keywords can be used as variable names. (Try this sometime: LET LET = 5: LET PRINT = 3:PRINT PRINT:PRINT LET … it works!) Some cannot, and BASIC will tell you about them. But ‘NOT’ cannot be the first three letters of any variable name. Example:
    10 LET NOTE = 5:PRINT NOTE
    
    If you enter that line and then LIST it, you will get
    10 LET NOTE = 5:PRINT NOT E
    
    because in an expression NOT is a unary operator that is never seen as part of a variable name. (In the LET, only a variable name is expected, so NOT is never seen.) This is the only “poison” keyword in Atari BASIC. (Note the use of ‘LET’ in several instances above. Generally, assignment to a variable name which starts with a keyword requires the use of LET to avoid confusing the syntaxer.)
  8. LOCATE and GET do not reinitialize their buffer pointer, so they can do nasty things to memory if used directly after some statements (e.g., they can change the line number of a DATA statement if used after a READ). FIX: reinitialize the pointer by using a STR$ function call (e.g., XX=STR$(0) works fine). Clumsy, but it works. PRINTing a numeric value works also (since PRINT calls STR$ internally). This fix is probably one you can ignore until it happens to you.
  9. An INPUT of more than 128 bytes (from disk, cassette, etc.) will write into the lower half of page six RAM ($0600–$067F). This is not a bug, it was designed that way. The lower half of page six was supposed to be available to BASIC, but someone at Atari forgot to tell someone else at Atari (and even two different memory maps in the Atari BASIC Reference Manual don’t agree). As a consequence, both Atari and user programmers have come to regard all of page six as their own and have put small assembly language programs there. FIX: don’t use the programs from $0600–$067F or don’t INPUT such long strings.

There are a few other minor bugs (e.g., you can say DIM A(32766,32766) without getting an error message), but, by and large, they won’t affect most programs. If anyone thinks they know of any other major bugs, let me know and I will try to provide a fix. Please let us know what topics you want covered.

COMPUTE! ISSUE 19 / DECEMBER 1981 / PAGE 89

INSIGHT: Atari

Bill Wilkinson
Optimized Systems Software
Cupertino, CA

Last month, we tackled some of the fundamentals of I/O under Atari’s OS. This month we will look at the extended disk operations available and will try our hand at writing a useful program in assembly language.

There simply isn’t space to repeat the charts given in last month’s article, so you will have to open to those pages: we will be referring to them often.

Atari I/O, Part 2: Disk File Manager

Notice that the title of this section is not “ATARI DOS.” There is a simple reason, which I expounded on before: Atari does not have a DOS. (But please don’t tell them I said so; they think they have to call it “DOS,” because that’s what everbody else calls it.) Atari has an “OS”; actually a much more powerful system than what is normally called “DOS” on microcomputers. And please recall from last month that the Atari OS understands named devices, such as “P:” and “E:”. The Disk File Manager (DFM) is actually simply a device driver for the disk (“D:”) device. It was written completely separately from Atari OS and interfaces to OS the same way any other driver does. In fact, there is nothing magic about the DFM. In theory, by the end of next month’s article you should know enough about Atari OS and the DFM to implement your own File Manager and to replace the one that Atari supplies you. (In theory. In practice, you had better know the principles of disk space allocation, I/O blocking and deblocking, and much more, before tackling such a job.) Even if you aren’t quite that ambitious, we hope that this series will give you some “insight” into how such things as BASIC’s I/O are implemented.

Extended Disk Operations

We should first note that most of the extended disk operations are documented in the Atari Basic Reference Manual in the section about the XIO command. There are two exceptions, NOTE and POINT, which were given special BASIC commands (and we will see why soon). Naturally, the Atari Disk Operating System II Reference Manual is pertinent, but it doesn’t really give more information about the internal workings of Atari’s OS than does the BASIC manual. Before delving into assembly language, let’s examine each of the extended disk operations in a little detail:

Error Handling

This may not be the best place to introduce this topic, but the information is needed for examples which follow. Space doesn’t permit a listing of all the I/O error codes, so we must refer you again to the BASIC and/or DOS II reference manuals. There are four fundamental kinds of errors that can occur with Atari OS:

On return from any OS call, the Y-register contains the completion code of the requested operation. A code of one (1) indicates “normal status, everything is okay.” (I know, why not zero, which is easier to check for? Remember, I said Atari was good, not perfect.) By convention, codes from $02 to $7F (2 through 127 decimal) are presumed to be “warnings.” Those from $80 to $FF (128 through 255 decimal) are “hard” errors. These choices facilitate the following assembly language sequence:

JSR CIOV   ;call the OS
TYA        ;check completion code
BMI OOPS   ;if $80-$FF,it must be an error

In theory, Atari’s OS always returns to the user with condition codes set such that the TYA is unnecessary. In practice, that’s probably true; but a little paranoia is often conducive to longer life of both humans and their programs.

A Real, Live Example

Believe it or not, you now have all the information you need to do from assembly language any and all I/O done by Atari BASIC and/or BASIC A+ (excepting graphics, but that’s coming…hold your breath). In an attempt to make you believe that statement, we will write a program in both BASIC and assembly language.

The BASIC Program

100 DIM BUFFER$(40)
200 OPEN #1,6,0,"D:*.*"
300 TRAP 700
400 INPUT #1,BUFFER$
500 PRINT BUFFER$
600 GOTO 400
700 CLOSE #1

This program will list all files on disk drive one (D1:) to the screen. This is exactly equivalent to using the “A” option of Atari’s menu “DOS” (and then hitting RETURN for the filename) or to using “DIR” from OS/A+. Admittedly, this program is easily improved. For example, replace line 200 with:

200 INPUT BUFFER$:OPEN #1,6,0,BUFFER$

and now you can choose to list only some files. You might also wish to send the listing to the printer (change PRINT to LPRINT). However, we will leave such changes as an exercise to the reader and discuss only our simplified version.

Please now refer to the listing in Program 1. Since it follows the scheme of the above BASIC listing, it is almost self-explanatory. A few words are in order, though. The equates at the beginning have been kept to a minimum; I refer you to the “SHOOT” listing in COMPUTE! #16 if you want a comprehensive list. (The mnemonics used are not all identical to those in the “SHOOT” listing; those shown are from our standard equates file.)

The program is intended to be called from BASIC via the USR function. However, no check is performed to see if the BASIC program were coded as (for example) PRINT USR(1600,0) instead of just PRINT USR(1600). (Note that 1600 decimal = 640 hex, the starting address.) If you would like to test this program with the BUG debug monitor, you should replace the RTS at the end of the program with a BRK before saying ‘G641’ (641 to avoid the PLA).

All errors, including an error on the OPEN DIRECTORY call to OS, are treated as end-of-file. A better program would verify the error status and print a message or some such. As an example of a minor improvement, at LINE700 one could save the Y-register (status) value in FR0 and zero in FR0+1 ($D4 and $D5), thus returning the error code to the calling BASIC program.

Notice that values stored into the IOCB for FILE0 (the console screen output) were stored directly into ICCOM, etc., without an X-register offset. This is perfectly valid, so long as the X-register contains the proper value on calling CIO. In fact, we could have stored the values for FILE1 (the directory) by coding (for example) STA ICCOM+FILE1. Obviously, this technique only works when one uses a constant channel number; but most BASIC programs and many language programs can use predefined channel numbers.

There isn’t really much more to say other than, “Try it!” It really does work. And, even if you don’t understand the concepts on first reading, actually entering the program and following the program flow and remarks might give you a painless introduction to I/O from assembly language.

The Easiest Way Of Making Room?

With an ATARI 400 or 800, there are many ways and places to find “safe” hunks of memory, places to put assembly language routines, player/missile graphics, character sets, etc. Many of the programs that I have seen involved techniques that I consider risky. For example, moving BASIC’s top of memory down requires that one do so only after issuing a GRAPHICS, command for the most memory-consuming graphics mode used in the program.

Other programs use machine language subroutines: but such subroutines must themselves have a place to stay. The best of such routines, however, approach the “official” Atari method. The approved method is normally used (by Atari) to add device drivers to the OS; in fact, the drivers for both DOS and the RS-232 ports follow these rules:

  1. Inspect the system LOMEM pointers.
  2. Load your routine (or reserve your buffer) at the current LOMEM.
  3. Add the size of the memory you used to LOMEM and
  4. Store the resultant value back into LOMEM.

If each routine, driver, etc., followed these rules, one could reserve more and more of memory without disturbing any following routine. (In fact, Atari drivers presume that LOMEM will never grow beyond 16K, $4000, or even less; but the principle holds.) Actually, there’s a hole in the above method; if the SYSTEM RESET button is pushed, OS goes through and resets all its tables, including the value in LOMEM. A “good” device driver can even take this into account, but we are going to make a few presumptions that are generally valid.

By now, you should realize that all of BASIC’s fundamental I/O commands are simply implementations of OS calls. PRINT becomes PUT TEXT RECORD; INPUT becomes GET TEXT RECORD; OPEN and CLOSE are essentially unchanged. In fact, the only BASIC commands that are not obvious clones of their assembly language counterparts are GET and PUT. Suffice it to say that these are actually simply special case implementations of GET BINARY RECORD and PUT BINARY RECORD (commands 7 and 11) where the buffer length is set to one byte.

Next month, we tackle the task of understanding how device drivers work, and we actually write a new and useful one that talks to a device built into all Atari machines (but one that Atari didn’t provide a driver for). And we haven’t forgotten the promise to show how graphics routines (such as PLOT and DRAWTO) are actually I/O routines.

The trick: BASIC always, repeat always, LOADs new programs at what it perceives LOMEM to be! Unfortunately, BASIC keeps its own MEMLOW pointer, which is loaded from LOMEM only on execution of a NEW, not on execution of LOAD or RUN and (significant!!!) not even in the case of SYSTEM reset. However, when there’s a will…

—ATARI BASIC—
10 LOMEM=743:MEMLOW=128
20 ADDR=PEEK(LOMEM)+256*PEEK(LOMEM+1)
30 ADDR=ADDR+SIZE
40 HADDR=INT(ADDR/256):LADDR=ADDR-256*HADDR
50 POKE LOMEM,LADDR:POKE LOMEM+1,HADDR
60 POKE MEMLOW,LADDR:POKE MEMLOW+1,HADDR:RUN "D:PROGRAM2"

—BASIC A+—
10 lomem=743:memlow=128
20 addr=dpeek(lomem):dpoke lomem,addr+size
30 dpoke memlow,addr+size:run "D:PROGRAM2"

The above listing is Program A, whose only purpose in life is to set up memory for the real program, Program B. “SIZE” is the amount of memory to be reserved. The program changes both the system and BASIC bottom-of-usable-memory Pointers so that either NEW or RUN “…” will recognize the reserved memory. The beginning lines of PROGRAM B follow:

—ATARI BASIC—
10 LOMEM=743:MEMLOW=128
20 POKE LOMEM,PEEK(MEMLOW):POKE LOMEM+1,PEEK(MEMLOW+1)

—BASIC A+—
10 dpoke 743,dpeek(128)

The only reason for these lines in PROGRAMB is in case of SYSTEM RESET. If the user types RUN after the reset, BASIC will copy its MEMLOW (the value which includes the reserved space!) into the system’s LOMEM, just so they agree with each other. A caution: I don’t know what will happen if you hit SYSTEM RESET as BASIC is in the process of loading PROGRAMB.

As far as I can tell, the only real problem that could occur would be if SYSTEM RESET were followed by a “DOS” command from BASIC. The OS would then get control, thinking that LOMEM had not been changed. In a normal running program environment, though, this is, at worst, unlikely, so this method seems more than adequate.

Columnar Output

A problem inherent in Atari BASIC is that the default tabbing (when using ‘PRINT exp,exp’) is ten columns while the screen is 38 columns wide. This produces an output something like this:

PRINT 1,2,3,4,5,6,7,8,9,10
1      2        3         4
 5      6        7         8
  9      10

Not too pretty. POKE 82,0 will change the left margin of the screen to zero (default is column 2), thus producing a 40 column screen and thus making 10 column tabbing an excellent choice. Unfortuntely, many TV sets have too much overscan to handle a true 40 column screen. Fortunately, Atari BASIC allows one to change the number of columns used in tabbing via a POKE 201,<tabwidth>. But the only factors of 38 are 19 and 2, meaning you can have 19 columns of 2 characters each or 2 columns of 19 characters each. Not much improvement so far.

Consider, though, the table of factors shown in Figure 1. As an example, if we have a screen 36 characters wide, we can have 2, 3, 4, 6, 9, 12, or 18 columns. And to get a screen 36 characters wide is easy: just POKE 83,37 (presuming that location 82 still contains a 2). So look at the list of factors, choose a screen width of N, and you can use a tab width equal to any factor. NOTE: a tabwidth of two will not print numerics in only two columns.

Finally, consider the flexibility available by judiciously choosing your tabwidth setting:

20 POKE 201,4:PRINT 1,2,
30 POKE 201,7:PRINT 3,
40 POKE 201,10:PRINT 4,5

Printing various values in a loop with this method can actually produce some quite readable columnar listings.

N Factors of N
40 2,4,5,8,10,20
39 3,13
38 2,19
37 none
36 2,3,4,6,9,12,18
35 5,7
34 2,17
33 3,11
32 2,4,8,16
Figure 1.
0000        1000        .TITLE "DEMONSTRATION FOR DECEMBER COMPUTE!"
0000        1010        .PAGE "SYSTEM EQUATES"
            1020 ;
0342        1030 ICCOM  = $342         ; 'COMMAND', IN IOCB
0344        1040 ICBADR = $344         ; 'BUFFER ADDRESS'
0348        1050 ICBLEN = $348         ; 'BUFFER LENGTH'
034A        1060 ICAUX1 = $34A         ; 'AUX BYTE 1' (OPEN MODE)
            1070 ;
0003        1080 COPN   =    3         ; 'OPEN' COMMAND VALUE
0005        1090 CGTXTR =    5         ; 'GET TEXT RECORD'
0009        1100 CPTXTR =    9         ; 'PUT TEXT RECORD'
000C        1110 CCLOSE =    12        ; 'CLOSE'
            1120 ;
0006        1130 OPDIR  =    6         ; 'OPEN DIRECTORY' SUB-COMMAND
            1140 ;
E456        1150 CIO    =    $E456     ; WHERE TO CALL ATARI OS
            1160 ;
            1170 ; NOTE: 0S/A+ users may omit lines 1010 thru 1160
            1180 ; if they use .INCLUDE #D:SYSEQU.ASM
            1190
0000        1200 FILE0  =    $00       ; IOCB NUMBER * 16
0010        1210 FILE1  =    $10       ; IOCB NUMBER * 16
00FF        1220 LOW    =    $FF       ; MASK FDR LSB OF ADDR
0100        1230 HIGH   =    $100      ; DIVISOR FOR MSB
0000        1240        .PAGE "BEGIN ACTUAL PROGRAM"
            1250 ;
            1260 ; housekeeping:
            1270 ;
0000        1280    *= $640            ;PUT ALL THIS IN SAFE PLACE
0640        1290    .OPT OBJ           ;WE DO WANT OBJECT CODE
            1300 ;
            1310 ; This program will list the
            1320 ; directory of disk D1: to the
            1330 ; E: device.
            1340 ;
            1350 ; Throughout, reference is made
            1360 ; to the BASIC demo program
            1370 ; which performs the same
            1380 ; functions.
            1390 ;
            1400 DIR
            1410 ; !!!! CAUTION !!!!
            1420 ; If this routine is to be used
            1430 ; from BASIC, the form MUST be
            1440 ; xxx=USR(addr) as this routine
            1450 ; makes no check on number of
            1460 ; parameter bytes !!!
            1470 ;
0640 68     1480        PLA            ; PULL OFF # OF BYTES
0641 4C7206 1490        JMP START
            1500 ;
            1510 ; We JUMP around the buffer,
            1520 ; Normally, the buffer would
            1530 ; be at the end; but we simulate
            1540 ; the BASIC program as closely
            1550 ; as possible
            1560 ;
            1570 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;
            1580 ;
            1590 ; 100 DIM BUFFER$(40)
            1600 ;
0028        1610 BUFLEN =      40
0644        1620 BUFFER *=     *+BUFLEN ; RESERVER 40 BYTES OF SPACE
            1630 ;
            1640 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
            1650 ;
            1660 ; 200 OPEN #1,6,0,"D:*.*"
            1670
066C 44     1680 NAME   .BYTE "D:*.*",0
066D 3A
066E 2A
066F 2E
0670 2A
0671 00
            1690 ; just a place to put filename
            1700 ;
            1710 START
            1720 ; begin actual program
            1730 ;

0672 A210   1740        LDX  #FILE1
0674 A903   1750        LDA  #COPN     ; THE OPEN COMMAND
0676 9D4203 1760        STA  ICCOM,X   ; IS SET UP
0679 A906   1770        LDA  #OPDIR    ; MODE 6, DIR OPEN
067B 9D4A03 1780        STA  IAUX1,X   ; THUS THE MODE
067E A96C   1790        LDA  #NAME&LOW
0680 9D4403 1800        STA  ICBADR,X  ; LSB OF ADDR
0683 A90A   1810        LDA  #NAME/HIGH ; AND MSB OF ADDR
0685 9D4503 1820        STA  ICBADR+1,X ; ...OF FLNM
0688 2056E4 1830        JSR  CIO       ; CALL ATARI OS
068B 98     1840        TYA            ; CHECK STATUS
068C 3035   1850        BMI  LINE700   ; HUH??
            1860 ;
            1870 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
            1880 ;
            1890 ; 300 TRAP 700
            1900 ;  SEE THE 'BMI' JUST ABOVE
            1910 ;
            1920 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
            1930 ;
            1940 ; 400 INPUT #1, BUFFER$
            1950 ;
            1960 LINE400
068E A210   1970        LDX  #FILE1
0690 A905   1980        LDA  #CGTXTR
0692 9D4203 1990        STA  ICCOM,X   ; 'INPUT' A LINE
0695 A944   2000        LDA  #BUFFER&LOW
0697 9D4403 2010        STA  ICBADR,X  ; LSB OF ADDR
069A 8D4403 2020        STA  ICBADR    ; OF WHERE LINE GOES
069D A906   2030        LDA  #BUFFER/HIGH
069F 9D4503 2040        STA  ICBADR+1,X ; AND MSB
06A2 8D4503 2050        STA  ICBADR+1  ; (WE ALSO SET UP ADDR FOR FILE #0)
06A5 A928   2060        LDA  #BUFLEN
06A7 9D4803 2070        STA  ICBLEN,X  ; BUFFER LEN
06AA 8D4803 2080        STA  ICBLEN    ; IS MAX WE USE
06AD 2056E4 2090        JSR  CIO       ; AND GO GET A LINE
06B0 98     2100        TYA
06B1 3010   2110        BMI  LINE700   ; "TRAP 700"
            2120 ;
            2130 ;;;;;;;;;;;;;;;;;;;;;;;;;;
            2140 ;
            2150 ; 500 PRINT BUFFER$
            2160 ;
            2170 ; note that PRINT automatically
            2180 ; uses file #0, so we will do
            2190 ; so also !!
            2200 ;
            2210 ; also note that we saved a few
            2220 ; bytes by setting up the buffer
            2230 ; address and length in 'LINE400'
            2240 ;
06B3 A909   2250        LDA  #CPTXTR
06BD 8D4203 2260        STA  ICCOM      ; PUT A LINE IS CMD
06B8 A200   2270        LDX  #FILE0     ; THE CONSOLE IS #0
06BA 2056E4 2280        JSR  CIO        ; TO THE I/O
06BD 98     2290        TYA
06BE 3003   2300        BMI  LINE700    ; OOPS?? HOW ???
            2310 ;
            2320 ;;;;;;;;;;;;;;;;;;;;;;;;
            2330 ;
            2340 ; 600 GOTO 100
            2350 ;
06C0 4C8E06 2360        JMP  LINE400    ; SELF EXPLANATORY
            2370 ;
            2380 ;
            2390 ;;;;;;;;;;;;;;;;;;;;;;;;;;
            2400 ;
            2410 ; 700 CLOSE #1
            2420 ;
            2430 LINE700
06C3 A210   2440        LDX  #FILE1
06C5 A90C   2450        LDA  #CCLOSE
06C7 9D4203 2460        STA  ICCOM,X    ; COMMAND IS 'CLOSE'
06CA 2056E4 2470        JSR  CIO        ; GO CLOSE THE PILE
06CD 60     2480        RTS             ; END OF ROUTINE
            2490 ;
            2500 ;
            2510 ;
06CE        2520    .END