COMPUTE! ISSUE 16 / SEPTEMBER 1981 / PAGE 70
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.
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.
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
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!
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.
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>":NEWBASIC 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:").
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":STOPUsing 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
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.
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.)
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.
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.|
|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.|
|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.|
|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.|
|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.
||Mnemonic label used by Atari for this field|
|EXTENDED COMMANDS: DISK FILE MANAGER ONLY|
—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.
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.”
10 LET NOTE = 5:PRINT NOTEIf you enter that line and then LIST it, you will get
10 LET NOTE = 5:PRINT NOT Ebecause 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.)
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
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.
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.
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:
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.
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.
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.
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:
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.
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