COMPUTE! ISSUE 44 / JANUARY 1984 / PAGE 184
Well, it’s the new year and, even though I am writing this months before New Year’s Eve, I’m going to make at least one resolution right now: I hereby resolve to write the articles which I have promised. (Except, of course, if I…naw, that’s not fair. I’ll even try to avoid those exceptions.)
So, in the spirit of that resolution, I’m going to deliver the fourth part of my series on writing self-relocatable machine language right now. This month. Immediately. After I feed you some tidbits first.
Recently, I have received several letters (“several” means more than four—I am seldom exactly deluged with mail) which all bear on one or two topics. Since there appears to be some interest in these two areas, I would like to touch on them this month. Normally, I acknowledge my readers by name when I answer letters. This time, however, several asked the same questions, and I am hesitant to single out any one letter. If you recognize, in this column, a response to a letter you wrote me, I offer my thanks for the ideas you have given me.
The questions about this topic ranged all the way from “How about a section for machine language beginners?” to “Are you planning any more about graphics from machine language?”
To begin, let me say that I do not intend to teach a tutorial machine language class through this column. A good tutorial would take about 200 magazine pages, minimum. That’s about what COMPUTE! allots me for two years’ writing. By the time the series were finished, I would hope that you would have been experienced programmers for over a year!
On the other hand, I will try to take the spirit of the questions to heart and include a little more material for those who are just beginning to learn machine language. (Unfortunately, that does not include this month’s article, but I feel committed to finishing the series.)
Several of you have asked me if I will write on how to do I/O and graphics from machine language. Unfortunately, I have already written a lot about these subjects (primarily from November 1981 to February 1982, but with many additions through the summer of 1982).
Alas, there is no beginner-level book which treats these subjects. Most of what I discussed in my articles is thoroughly explored in Atari’s Technical Notes and Operating System documentation or De Re Atari, but you need to be well-versed in 6502 machine language before tackling either.
Probably the most popular books about the 6502 are those by Rodnay Zaks. My personal opinion is that they are good, but not great books. So, after you have digested Richard Mansfield’s Machine Language for Beginners (COMPUTE! Books), you probably should be very careful about what book you pick up from your dealer’s shelves. Pick one which appears appropriate to your level. But keep watching: More books are on their way.
I had promised that I would say no more on these topics, since there is obviously something of a conflict of interest for me here. (Atari hasn’t bought our DOS XL, but some of the other disk drive manufacturers have.) But I have received several cogent questions and comments, and I will try to answer them as honestly as possible.
First, I heard from a couple of people that the 1050 drive does not support 32 sectors per track in its pseudo-double-density mode. The claim is that it only supports 26 sectors per track, a substantial reduction in capacity. Since I don’t have a 1050 drive or the final version of DOS 3, I cannot directly verify or dispute this claim. (Is it possible that this claim is a result of an opinion which I myself expressed to a users group last spring?)
I can only reiterate that it was an examination of a preliminary copy of DOS 3 which resulted in my comments.
The other letters I received either chided me for not giving more details on DOS 3 or simply asked whether it would work with…well, almost anything (Atari 810 drives, RAMDISKs, Mosaic boards, etc.). First, let me state that I have not been able to exhaustively test DOS 3. The preliminary version works on an Atari 800 with an 810 drive. Beyond that, I cannot say.
DOS 3 achieves its random access file capability by segmenting the disk into 128 blocks of 1K each. Obviously, with so few blocks, one can keep a pointer to each block in memory at all times. In fact, the VTOC (which is a bitmap on DOS 2) is also the file block map (which doesn’t exist on DOS 2, hence no random files), all nicely packed into only 128 bytes of your computer’s memory per drive.
And that is beginning to get more technical than I meant to get in this section, but let me close by noting that expanding this scheme to a 5 megabyte drive would imply either 40,000 bytes per block on the disk (and remember, a block is the smallest possible file size) or 10,000 bytes of VTOC and file map per drive in your main memory (in order to maintain the 1K block size). And that is why I said in my previous articles that DOS 3 does not expand well.
Anyway, I found it surprising that Atari would introduce the double-sided drives of the 1450XLD with DOS 3. But maybe I’m going to be surprised again.
Once again, I am indebted to Steve Lawrow, the author of our MAC/65 assembler for telling me of another discovery about Atari BASIC which I shall share with you.
I have been traveling around demonstrating our new BASIC XL to various user groups; and, quite naturally, I have found several quick and easy programs which show off the language. One of my favorites is the following little gem;
1 REM 2 REM 3 REM … 99 REM 100 POKE 20,0 101 IF I<200 THEN I=I+1:GOTO 101 102 PRINT PEEK(20)
The object of this little gem is to get a bunch of do-nothing lines (in fact, 99 REMarks) in a program and then see how much they slow down the loop in line 101. Location 20 is the 1⁄60 second clock tick (1⁄50 second in countries using 50Hz power systems), so the result of lines 100 and 102 is to print out the elapsed time in clock ticks.
Well, I usually run this programette in Atari BASIC first, Atari Microsoft BASIC second, BASIC XL in slow mode third, and BASIC XL in FAST mode last. (See the chart for timings.)
Steve mentioned to me, though, that the timings of Atari BASIC (and slow-mode BASIC XL) were dependent on the line numbers chosen. Skeptically, I renumbered the program to look like this:
1 REM 2 REM 3 REM … 99 REM 4000 POKE 20,0 5000 IF I<200 THEN I=I+1:GOTO 5000 6000 PRINT PEEK(20)
Sure enough, all the BASICs (except, naturally, BASIC XL in FAST mode) speeded up a little (again, see the chart). Why?
I know the answer for Atari BASIC and BASIC XL, and I suspect it is the same answer for Microsoft BASIC. When these BASICs need to make a line number search, they place the line number being searched for in a particular memory location. Then they search through the program, a line at a time, looking for a match on the numbers. As they search, though, they always check the high bytes of the line numbers first. If the high bytes do not match, they don’t bother to check the low bytes.
In our first example, since all the line numbers were less than 256, all the high bytes were the same, so the search took slightly longer. In the second example, though, the GOTO statement caused a search for line number 5000, whose high byte is never the same as those of any of the other lines. Bingo, fast search speed.
What does this mean? When writing in BASIC, it might be a good idea to modify the old traditional line-numbering-by-10. Purposely break your program up into sections so that the target lines of GOTOs and GOSUBs all differ by at least about 300, and you will help BASIC do its searching a bit faster. (And even though BASIC XL in FAST mode is not affected by these foibles when working with absolute line numbers, even it will be helped when in slow mode or when you use variable names or expressions as GOTO/GOSUB targets.)
And, incidentally, you might remember that the Microsoft version of this program must be typed exactly as shown to get these timings. Using longer variable names, more spaces in a line, more variable names, etc., will significantly slow down Microsoft BASIC. Often to the point where it is slower than Atari BASIC.
Anyway, here is the chart of timings. The Microsoft BASIC Integer version timings were obtained by appending a % to all variable and constant usage in lines 101 and 5000. Try some timings like this yourself. You’ll be amazed.
Timings For The 99 REM Benchmark Atari
BASICMicrosoft
Fltg.Pt.Microsoft
IntegerBASIC XL
slowBASIC XL
FastGOTO 101 178 169 155 125 35 GOTO 5000 162 160 145 110 35
Since it has been three months since Part 3 of this sort-of series appeared (COMPUTE!, September 1983), let me briefly summarize why self-relocatable machine language (ML) is desirable:
Also, let me summarize some of the rules for “Safe Relocatable Techniques,” as presented in September:
Finally, let me remind you that I promised to tell you how to utilize more than 255 bytes of relocatable storage and how to generate pointers to such storage without the “benefit” of help from a calling BASIC program. I shall attempt to fulfill my promise.
The techniques I will discuss here require a very small segment of nonrelocatable ML as well as one or (better) several zero page pointers. If you are in really dire straits, you can make do with temporary locations for both those requirements, but if possible you should find a way to preserve the required memory exclusively for your routine. In fact, the rest of this discussion assumes that you have managed to preserve the locations.
You must have a subroutine, located at a fixed location, which looks like this:
BASE = $CE *= $680 FINDME PLA STA BASE +1 PLA STA BASE PHA LDA BASE+1 PHA RTS
Note that I have placed this routine in the infamous Page 6 and have used a fixed zero page location. These choices are for convenience, for illustration. Feel free to make your own choice of locations.
And just what does this routine do? How does it work? Quite simply, it finds the address of the program which called it. More precisely, it finds the address of the last byte of the three-byte JSR instruction with which a relocatable program calls it. An illustration of the calling program will help:
*= ???? START JSR FINDME BASEPT1 = *-1 … LDY #DATABYTE-BASEPT1 LDA (BASE),Y … DATABYTE .BYTE 99
Do you follow this? When FINDME is called via the JSR, it places the address of BASEPT1 into the zero page location called BASE. Then the Y register is loaded with the offset from BASEPT1 to DATABYTE and used an index for the LDA instruction. (This is similar to the technique discussed in Part 3, but it could only be used from BASIC USR calls.)
The limitation of this technique is that the data location (for example, DATABYTE above) must be located no more than 255 bytes away from the JSR (for example, BASEPT1). If you are writing a package of several small routines, this may not prove to be a limitation. After all, each routine could call FINDME if needed, and each routine could thus have its own storage areas, located no more than 255 bytes from the respective call to FINDME. If you are writing a subroutine library or a device driver, this might prove to be a very worthwhile option.
Note the side “benefit” to the scheme: If you call FINDME each time you enter a routine, then BASE may prove to be a really very temporary location and can be shared with other routines.
So far, so good. But suppose that you really do need a large data area or program, all self-relocatable. Well, then, your program might have to do this:
*= ???? DATABASE = $CC START JSR FINDME BASEPT1 = *-1 OFFSET1 = DATABYTES-BASEPT1 … CLC LDA BASE ADC #OFFSET1&255 STA DATABASE LDA BASE + 1 ADC #OFFSET1/256 STA DATABASE+1 LDY <some offset in DATABYTES> LDA (DATABASE),Y DATABYTES .BYTE 1,2,3,4,5,6,7,8,9,10
Even more confused? You have a right to be. Here, we actually develop the base address of a data area and place it in a new zero page location. Now we can access the data area from anywhere in our self-relocatable ML by simply placing an offset within that data area into the Y register. Again, this limits the size of access to 256 bytes (the range of values the Y register can take on), but now the program can be as large as desired.
Finally, what happens if you actually do have a data area larger than 256 bytes? There are several possible solutions, none of them easy. If no “array” within the data area is larger than 256 bytes, you could simply develop several zero page pointers— one for each group of 256 bytes or less—using the ADC #OFFSET technique presented above.
If you have a single array or table which is larger than 256 bytes, the chances are that you have already developed some method of addressing into it (since the 6502 limits you to index sizes of 0 through 255, unless you play with indirect-Y addressing and calculated zero page pointer values). You need only use the contents of DATABASE, as generated above, in place of an absolute address for the start of the array or table, and your address calculations will be similar or even identical.
If you are lost at this point, don’t worry. Much of what I just said will suddenly be meaningful as you write more and more advanced machine language programs. Just keep this article for handy reference.
Suppose you want to call subroutines within your self-relocatable ML. How do you do it?
Of course, if the subroutine is at a fixed location (in ROM somewhere), you need do nothing special. The JSR instruction insists on an absolute address, and you simply supply one. But what happens if the routine you want to call is itself part of the self-relocatable ML?
Advice: Avoid doing what I am about to describe if you possibly can. However, if you need to write ML which must use these techniques, read on.
First, you could simply write some self-modifying ML. An example:
START JSR FINDME BASEPT = *-1 ROUTINE1 = ROUTINE-BASEPT CALL1 = CALL +1 - BASEPT LDY #CALL1 CLC LDA BASE ADC #SUB1&255 STA (BASE),Y INY LDA BASE+1 ADC #SUB1/256 STA (BASE),Y CALL JSR 0; ADDRESS WILL BE GENERATED ROUTINE RTS
Simply, did he say? Well, it’s not as bad as it looks. After all, if we could generate the address of a table and place it in zero page, why can’t we place a subroutine’s address directly into our ML? Of course, we must do the placing indirectly, since even the address of the JSR instruction is self-relocatable. Did you note that CALL1 is an offset to the first address byte in the instruction? It wouldn’t do to modify the instruction byte!
Another way of doing JSRs like this might be to place yet another small routine in nonrelocatable memory. You could (1) load the A and X registers with the offset to the desired subroutine, then (2) JSR to the non relocatable routine which would calculate the actual address you desired, and (3) JMP to that location. When the subroutine returned, execution would continue at the instruction after your JSR.
COMPUTE! ISSUE 45 / FEBRUARY 1984 / PAGE 162
This month we’ll begin to explore some of the techniques involved in creating a general-purpose formatted screen I/O routine in BASIC. “And just what is a general-purpose formatted screen I/O routine?” you quite rightfully ask.
Briefly, what I am trying to do is produce a method whereby the programmer may specify certain areas of the screen as “label” or “title” areas, which may not be modified by the user. Other parts of the screen then become the Input/Output (I/O) areas. The user will not be able to change any part of the screen except the designated I/O areas, but he or she will be able to “randomly” access any area and change it. When the screen is filled in properly, the user pushes a single key (I intend to use ESCape) and the screen is automatically read into data variables in memory, where the program may process them or write them to disk.
The concept is certainly nothing new. Mainframe installations such as airline reservation systems have been doing exactly this for years. And I am sure that programs already exist for the Atari computers which work in a like fashion. So why am I writing these routines? For practical use here at OSS. Believe it or not, we intend to have a sales order entry system, complete with accounts receivable and general ledger interface, up and running on an Atari computer.
Surprised? Didn’t think the Atari was capable of such sophisticated work? Truthfully, as the machine is shipped from Atari, it is not. The big missing link is large amounts of disk storage. We intend to use at least two double-density, double-sided drives (or equivalents), and may find that we need three or four.
And why are we doing this on an Atari computer, instead of a CP/M or MS DOS machine? Quite frankly, because we have the equipment already paid for and because we have yet to see an adequate order entry system even for such “bigger” machines.
Anyway, so far I have written three of the workhorse subroutines of my formatted screen routines: (1) Display fixed information at fixed locations on the screen, (2) Display variable information (presumably obtained from a disk file) on the screen, (3) Edit the variable information (or enter new information).
Routine number three is both too big and too complicated to put in this month’s column. Also, it runs fine in BASIC XL; but when I tried to translate it to Atari BASIC, it got bigger and slower and may not be too usable. If there is enough interest, I might be persuaded to write about it in a future column. Routines 1 and 2, though, are so surprisingly small, simple, and elegant when written in Atari BASIC that I felt you would enjoy seeing them. So let’s look at them before explaining how they work.
Routine 1: Fixed Setup 30000 REM set up fixed screen areas 30010 TRAP 30020:DIM DATA$(50) 30020 TRAP 40000:RESTORE PTRDATA 30030 READ DATA$:IF DATA$=M$ THEN RETURN 30040 POSITION VAL(DATA*(1,2)),VAL(DATA$(3,4)) 30050 PRINT DATA$(5);:GOTO 30030
Routine 2: Variable Display 31000 REM display variable data areas 31010 TRAP 31020:DIM DATA$(50) 31020 TRAP 40000:RESTORE PTRDATA:QPTR=1 31030 READ DATA$:IF DATA$="*" THEN RETURN 31040 POSITION VAL(DATA$(1,2)),VAL(DATA$(3,4)) 31050 PRINT SCREEN$(QPTR,QPTR-1+VAL(DATA(5,6)); 31060 QPTR=DPTR+VAL(DATA$(5,6)):GOTO 31030
Listing 3: A Tester For The Routines 100 DIM SCREEN$(200) 110 SCREEN$="ZUCKERMAN 95099C" 200 REM fixed data 210 DATA 0810Name: 220 DATA 0412Zip Code: 230 DATA 0816Code: 240 DATA * 300 REM variable data parameters 310 DATA 151010 320 DATA 151205 330 DATA 151601 340 DATA * 400 REM the actual test program 410 GRAPHICS 0 420 PTRDATA=200:GOSUB 30000 430 PTRDATA=300:GOSUB 31000 440 REM just loop here for now 450 GOTO 440
Even though I have presented this example as three separate listings, if you would like to see its effects, you should type all the lines into a single program.
So, what’s the secret of this simple yet (according to me) elegant program? Surprisingly enough, I find myself returning to the concept I explored in my very first COMPUTE! column (September 1981, for you “regulars”): addressable DATA statements. Very few BASICs have addressable DATA statements, yet when I look at this program I cannot understand why they don’t.
The lines to look at carefully are 30020 and 31020, where the program says “RESTORE PTRDATA”. When either of these routines is called, it expects that the variable PTRDATA will contain the line number of the beginning of some DATA statements which it must begin processing. So let’s look at those DATA statements first.
In lines 210 through 230, we define the fixed fields on the screen as starting at a particular horizontal (X) position (the first two digits) and a particular vertical (Y) position (the next two digits). Notice how line 30040 reflects this usage with the VAL functions it uses in conjunction with the POSITION statement.
Similarly, in lines 310 through 330, the definitions of the variable fields are expressed as horizontal position (first two digits), vertical position (next two digits), and field length (last two digits). Again, lines 31040 through 31060 reflect these usages via VAL functions.
If you are wondering why I am making such a fuss over these two little routines, especially when it takes so much programming to prepare to use them, you probably haven’t typed in the program to see what it does. Or, to be fair, you haven’t seen the best part of all, the onscreen editor that’s too big for this month’s column.
And why am I going to this much trouble, when I could use PRINTs and INPUTs to do the same thing? Two reasons: (1) If I use PRINT and INPUT, I have to write the entire code each time in a form which makes my programs hard to read and understand. (2) The INPUT statement as implemented on most BASICs is a disaster, and Atari BASIC is no exception. There is no way, when using INPUT, to keep the user from hitting screen-editing keys or from entering too much or too little data.
Did I mention that the screen editing routine I have written allows the programmer to specify, via simple DATA statements, not only where and how big the variable data fields are on the screen but also what attributes they may have (for example, numeric, alphabetic, dollars and cents, etc.)? I didn’t? Are you more interested now?
Next month we’ll continue our examination of screen I/O by making test runs of the programs.
COMPUTE! ISSUE 46 / MARCH 1984 / PAGE 153
In this column, we continue the discussion of formatted screen techniques.
This is another one of those “Did you know?” tidbits. Did you know that when you use GRAPHICS 0 from Atari BASIC you have automatically opened the screen for GETting and PUTting via file number 6? It’s true, and it is because Atari BASIC does not check the mode number for the GRAPHICS statement.
GRAPHICS 0 is thus exactly equivalent to:
OPEN #6,12+16,0,"S:"
So if you need to GET or PUT from or to the screen, you can do it directly to file #6 without any further ado.
Unfortunately, there are a few gotchas involved in using GET and PUT to the Atari Screen graphics driver (“S:”), some of which you may have seen before, so let’s discuss them, as well as ways around them.
The first problem is that if you use PUT #6 combined with POSITION statements or PRINT statements, you will probably end up leaving some inverse video spaces (white boxes) around on the screen, as Program 1 illustrates. This is because the screen graphics driver works almost (but not quite) like the screen editor driver (“E:”, the normal channel #0 device which PRINT and INPUT use). Unfortunately, “S.” can’t seem to handle its cursor properly, so it may be best to avoid using PUT #6.
Program 1: Problems With PUT #6 10 GRAPHICS 0 20 POSITION 30*RND(0),20*RND(0) 30 PUT #6,65+20*RND(0) 40 GOTO 20
How can we avoid PUT #6 if we have something we need on the screen? Simple. Use PUT#0 (if you have BASIC XL or any other product which allows PUT to file #0) or PRINT. If you use PRINT, of course, you will have to use
PRINT CHR$(X);
in place of PUT #0,X. And why does outputting to file #0 work where using #6 does not? Because #0 is opened to “E:”, and there are several subtle differences between “E:” and “S:” where cursor positioning and character I/O are concerned.
Unfortunately, while the problems with PUT #6 are fairly easy to get around, the problems with GET #6 must be dealt with directly. And why can’t we simply use GET #0 in place of #6 here, as we did with PUT? Because, when you ask “E:” (channel #0) for a character, it waits until the user actually types in an entire line—terminated by a RETURN character—before returning anything at all to its caller (you are the caller via BASIC in this case).
The whole reason for using GET #6 is to allow ourselves to read individual characters from the screen. We simply can’t use GET #0 or anything else which accesses “E:”.
But this is putting the cart before the horse a little. Before “fixing” the problem, let’s illustrate it with Program 2.
Program 2: Problems With GET #6 10 GRAPHICS 0 20 PRINT "ABCDEFGHIJKLMNOP" 30 FOR I=2 TO 12:POSITION 1,0 40 GET #6,CHAR 50 POSITION 20,20:PRINT CHAR 60 FOR J=1 TO 200:NEXT J:REM just a delay loop 70 NEXT I
I hope you actually stopped while reading to try out that listing. Bizarre, isn’t it? It seems that you can’t GET data from the screen without destroying it. Now, most of the articles which I have seen which note this problem suggest that the only safe fix is the following:
That fix will indeed work, but I would propose that an alternate solution is to simply print a “left arrow” (backspace) and then the character, thus avoiding the extra POSITION statement. In Program 2, we could simply add this line to fix things up:
45 PRINT CHR$(30) ; CHR$(CHAR);
Now that you know how to properly PUT and GET to the screen, you probably have a fair idea of how I built my onscreen editor. It isn’t too hard to do anything you want to the GRAPHICS 0 screen, once you get past the quirks in the Atari OS.
Probably every BASIC book you have ever seen tells you to properly nest FOR/NEXT loops. Aside from the neatness of it, there are some good and practical reasons. Consider Program 3.
Program 3: Obviously Invalid Nesting 10 FOR I = 1 20 FOR J = 1 TO 3 30 NEXT I 40 NEXT J
Very few of you would deliberately write a BASIC program which looked like that. Even with the indentation I have given it, it should be obvious that something is wrong.
And, yet, it is fairly easy to write a program which will look proper and yet have the effect of that listing! Don’t believe it? Try Program 4.
Program 4: A Subtle Problem 100 REM Program task: Print all numb ers from 1 to 9, in a nested loo p fashion. When the first sum o f 15 or 101 REM greater is found, cease the operation. When the sum is 10 o r more, don’t print the result. 102 REM Repeat for the products of t he same numbers in the same fash ion. 110 PRINT "I","J","SUM" 120 FOR I=1 TO 9 130 FOR I=1 TO 9 140 SUM=I+J 150 IF SUM>14 THEN 200 160 IF SUM>10 THEN 190 170 PRINT I,J,SUM 180 NEXT J 190 NEXT I 200 PRINT "I", "J", "PRODUCT" 210 FOR J=1 TO 9 220 FOR I=1 TO 9 230 PROD=I*J 240 IF PROD>14 THEN 290 250 IF PROD>10 THEN 280 260 PRINT J,I,PROD 270 NEXT I 280 NEXT J 290 END
Now this looks perfectly harmless, if somewhat pointless, right? It looks like it should work fine. Yet, if you will type it in and RUN it, you will find that line 280 will give you a NEXT WITHOUT MATCHING FOR error the first time it is reached. How? Surely line 210 is the FOR which matches the NEXT of line 280.
If Atari BASIC were a compiler language, it would probably execute that program correctly. However, since it is an interpreter, it must work within the strictures of that mode. Interpreters, by their very nature, cannot easily keep a history of all NEXT usages. It is enough that they remember where the FOR statements are, so that when a NEXT is encountered they can go back to the FOR to execute the loop another time.
Consider, then, the dilemma of the poor interpreter in the above program. In line 160, we are asking it to bypass the end of the inner FOR loop (since we know we are done with the previous usage of it) and start the next iteration of the outer loop (NEXT I). But wait. There is still a FOR J on the runtime stack, yet we are executing a NEXT I. What can we do?
Atari BASIC does what most modern “smart” BASICs do. If it finds a loop variable NEXT which does not match the last FOR on the stack, it presumes that the user has jumped out of the inner loop (as indeed we have here) since that is a common occurrence. So BASIC looks backward in the stack for a matching FOR. Eureka! It finds the FOR I only one level down in the stack, without any intervening GOSUBs, so its supposition seems confirmed. All works well.
However, look at line 150, wherein we jump out of all the loops. What have we left on the run-time stack now? Obviously, both a FOR I and a FOR J. Well, no real problem. After all, we know we jumped all the way out of the loop, don’t we? We don’t. Why not? Because a BASIC interpreter must presume that the BASIC programmer knows what he or she is doing. It is, unfortunately, perfectly legal to jump in and out of a loop in Atari BASIC. It is, in fact, even legal to have more than one NEXT for any given FOR.
So what can BASIC think when it gets to line 210 but that it is starting the inner FOR loop over again? It leaves the FOR I in place (for all it knows, the next statement it encounters might be a NEXT I) and adds a new FOR J.
Disaster really strikes in line 220. Poor BASIC is trying its best. Knowing that it is not uncommon for BASIC programmers to jump out of loops or to jump to the beginning of a loop to start it again, BASIC almost has to presume that the FOR I of line 220 is the beginning of a new outer loop. Besides, it already has a FOR I on its runtime stack. How can it allow another?
Well, if this is the beginning of a new outer loop, better throw away the old outer loop and any of its inner loops. Say good-by to the old FOR I and FOR J; we’re ready for another outer loop with a new FOR I. Right?
Wrong. But BASIC doesn’t know about it while it stays in the FOR I loop, since it encounters no other FORs or NEXTs. In fact, the entire loop executes nicely with no problems, and the FOR is properly removed from the stack when the last value of I is reached. Did you notice that the stack is now empty?
Where did this NEXT J come from? FOR J was an inner loop and was thrown away when the outer loop was restarted.
Actually, Atari BASIC is not a culprit here. Virtually every BASIC will have this same problem unless it makes a pre-pass through the user’s program to detect possible inconsistencies (such as jumping out of nested loops). In point of fact, Atari BASIC is almost a good guy here. Recognizing that even with the best interpretation we could do, we could not prevent users from writing (or needing to write) structures such as I have shown you, we designed a “fix” into Atari BASIC.
The fix takes the form of the POP statement. POP simply removes the last level of the runtime stack. In Program 4, the easiest fix is
150 IF SUM>9 THEN POP :POP :GOTO 200
(and a similar fix is needed in line 240, of course).
Notice I said that was the easiest fix. POP is usually not the best fix. Generally, you can write good and properly structured programs, with properly terminating FOR loops, without ever resorting to such extreme measures as the POP statement. Still, it is comforting to know that POP is around. Personally, I tend to use it whenever an error condition occurs and I want to get all the way back out to (for example) the menu level without leaving nasty GOSUBs or FORs on the runtime stack.
A curiosity: Did you notice that if the nesting in lines 200 through 290 is reversed (that is, if the FOR I occurs before the FOR J), the program will work correctly? Do you see why? Fundamentally, because you are now doing what BASIC expected you to do. Go try this example both ways on a Commodore or Radio Shack or whatever computer. Does either method work? I’d be interested in knowing.
If you ever get a NEXT WITHOUT MATCHING FOR error, look for this kind of structure in your program. If you find it, you can fix it with POP, but wouldn’t it be nicer to write the program correctly?
A footnote to all of that: Can you begin to get an appreciation of what language designers must contend with? It is not enough that a language do what it is expected to do. A good language will come halfway toward helping its users over the rough spots.
Here’s a loader for binary object files which will place them in memory at the location they were assembled for. The routine is written entirely in Atari BASIC, so it is slow. Next month, we’ll present the same routine written in machine language, perhaps even in a version callable from a BASIC program (just to speed things up).
Atari object files have a fixed and reasonable format. The first two bytes of the file are always $FF and $FF (255 and 255, in decimal). They serve as a check that the file is indeed an object file. The next two bytes are the starting address in memory of the first (and perhaps only) “segment,” while the following two bytes are the ending address of the segment. These header bytes are followed by enough object bytes to fill up the memory from the starting address through and including the ending address.
If a file has multiple segments, each segment may or may not (programmer’s option) be preceded by the same $FF and $FF bytes. Each segment must always be headed by both a start and an end address. Without further ado, then, the loader program, Program 5.
Program 5: Load A Binary Object File 100 REM binary object file loader 110 DIM NAME$(30) 120 PRINT "WHAT FILE TO LOAD "; 130 INPUT NAME$ 140 OPEN #1,4,0,NAME$ 200 REM get and check header 210 TRAP 400 220 GET #1,LOW:GET #1,HIGH 230 TRAP 40000 240 IF LOW=255 AND HIGH=255 THEN GET #1,LOW:GET #1,HIGH 250 START=LOW+256*HIGH 260 GET #1,LOW:GET #1,HIGH 270 QUIT=LOW+256*HIGH 300 REM read in a segment 310 FOR ADDR=START TO QUIT 320 GET #1,BYTE 330 POKE ADDR,BYTE 340 NEXT ADDR 350 GOTO 200:REM try for another segment 400 REM trapped to here, assume end-of-file 410 CLOSE #1
Since I’m running out of time and space this month, I will let the explanation of object file format, above, serve for now as an explanation of this program. I will warn you, however, that I cheated a bit in line 240 to make the multiple segment loading easier. The routine will try to load anything into memory, whether or not it is truly a binary object file. If your memory dies a violent death (fixable only by turning power off and back on), you tried to load something other than an object file with this. Naughty.
Next month some notes on destination strings in Atari BASIC. And maybe—just maybe—we’ll play around with Atari screen I/O a little more.
COMPUTE! ISSUE 47 / APRIL 1984 / PAGE 134
Well, here it is April again. Those of you who read my April column last year may recall that I devoted much of my space to an April Fool announcement of the “new Atari COBOL.” Would it surprise you to learn that this year I will also “announce” some new products from Atari?
We’re going to be exploring some medium hefty programming logic (in machine language) next month: How to use your 1050 disk drive in enhanced density with Atari DOS 2.0s. For now, though, let’s plunge into some wild speculations, rumors, and April fun.
Since last year’s April announcement was about some literally unbelievable software, it seems only fitting that this year we make a hardware announcement that’s almost as doubt-provoking.
By the time you read this article, Atari will be shipping at least the first two of three magnificent new machines. These machines, while maintaining almost full compatibility with existing Atari hardware and software, add the full power of an intelligent peripheral expansion bus. Imagine an Atari computer hooked up to a 5- or 10-megabyte hard disk drive, a true parallel printer interface, a high-speed modem, and maybe even a CP/M emulation package.
I mean, we’re talking about possibly moving data to and from a disk drive at 30,000 to 60,000 bytes per second! Imagine taking less than two seconds to load the largest possible programs. And perhaps talking via a serial interface (or, better yet, a local network) to one or more other computers at the same time—at data rates perhaps three to ten times what an 850 Interface Module is capable of.
Of course, if you are a realist, you will say, “Okay for the hardware. But what about software and software compatibility?” Would you settle for a smart peripheral bus that intercepts the OS (CIO) if you are trying to do I/O via the old serial bus. You know, the cable that links your 400/800/1200 to the disk, printer, etc.? Could you accept the fact that it checks to see if you have (for example) moved “D1:” to your hard disk drive and sends the request there instead of to your 810? All automatically?
Not enough? How about if these new machines even provided ways for third-party hardware vendors to add their own boards and automatically link in device handlers for them? Imagine a music synthesizer accessed as simply “M:”, thus easily callable from even Atari BASIC. Could any computer manufacturer possibly design such a well-integrated system?
How about if the new machines even came with a faster math package, so that they were the fastest computers in the home computer marketplace? (I choose to define a home computer as one which costs less than $1000, including at least a disk drive.)
Well, you knew it couldn’t last, didn’t you? Sigh. But it was nice to dream for a paragraph or two, wasn’t it? Now, are you ready for the bitter reality?
Surprise! This is my April Fool gag for this year: Almost everything you just read about the new machines is the absolute truth. Honest. No gag.
In fact, as I write this article in January, the machines I have described to you are arriving in stores by the truckfuls. And why, you ask, haven’t you seen these wonder computers advertised? Ah, but you have. They are called the 600XL and the 800XL (with big brother 1450XLD still to come). But if all I claimed is true, why hasn’t Atari proclaimed it to the computer world as the greatest advance ever in home computers? Now there is the April Fool question.
If Atari can solve some advertising and delivery problems, I think you will see a wealth of capabilities added to the new XL machines second only to the selection available to Apple II owners.
Oh, yes. I did throw one April Fool joke into the description above. Can you guess what it is?
The descriptions of the Atari super machines were accurate except for one April Fool joke. Sigh. Unfortunately, the part about the advanced, fast BASIC being built-in is still just a dream.
COMPUTE! ISSUE 48 / MAY 1984 / PAGE 147
A month or two ago, I stated that I couldn’t possibly teach beginning machine language programming in this column—it would consume my entire output for a year or more. And yet I continue to get letters that ask me “How do you learn to write programs?”
I believe that those who ask the question are not asking for a tutorial on the foibles and pitfalls of the FOR-NEXT loop. Nor are they really asking about the intricacies of the 6502 instruction set. Most of them have already mastered the tutorial-level material on their chosen language. What these perplexed people are really asking is “What good is all this programming stuff, anyway?”
And that is not really surprising. So many tutorials tell you how to write a program to do such and such. So few discuss why. Too often, learning to program is approached like learning a foreign language. Memorize the conjugations and punctuation; put sentences together like this; and if someone asks you “G’dye moya k’neega?” you know what to answer (providing you were studying Russian instead of Spanish).
But the need to learn human languages is obvious: The first time you feel hungry in Paris, you can ask for directions to a restaurant in your best Berlitz French. You don’t have to “design” a conversation. Not so with learning to program: “Okay, now I know all these neat keywords and syntax and punctuation. How do I start a conversation?” Well, as I hinted above, the secret is that you must design a program.
To some, this design process is simple and obvious. Others never really get the hang of it. (Would it surprise you to learn that many professional programmers never become expert at designing? They make their living implementing other people’s designs.) And many, like myself, become somewhat proficient at a few kinds of designs while remaining incompetent at others. (My lament: I don’t think I will ever achieve the level of creativity necessary to design a really good game.)
Now, all the above philosophizing surely has some purpose, you hope. Indeed, I think it does.
I have been promising for a few months now that I would provide patches to allow the Atari 1050 drive to work in enhanced mode with good old Atari DOS 2.0s. Well, I finally gathered enough information to begin the task, and I thought you might enjoy looking over my shoulder while I tackle the problem.
This will be a kind of short diary of what I have gone through. There have been more side-tracks and bugs and flat-out boo-boos than I can find room for here. And I won’t even tell you how many assemblies I have made (though I will say I made about 10 or 12 just looking for the best of several possibilities for a series of shift instructions).
Even though I admire and strive for a “clean” design, I am apt to take the course of least resistance if I am confident it will work properly. With that in mind, then, let us begin tackling our task.
Note: I will make frequent reference to the listing of Atari DOS 2.0s as published in the book Inside Atari DOS from COMPUTE! Books. Page numbers and line numbers in square brackets [131: 1350] refer to the book.
It will not be necessary to own the book to understand most of what is going on, but having the book available will make it easier. Also, if you do not understand machine language, neither the book nor my explanations will be easy to follow, but you can still use the results (which will appear next month).
The first thing we must always do is define the task. Here, that is deceptively simple to do: Make the enhanced density mode of the Atari 1050 drive work with Atari DOS 2.0s.
The next step is much harder: Design the implementation of the task. And, actually, this single step consists of many substeps. For example, let’s first investigate the facts which I knew when I started.
The drives:
Item: An Atari 810 drive has 40 tracks of 18 sectors of 128 bytes each. That’s a total of 720 sectors.
Item: An Atari 1050 drive has 40 tracks of 26 sectors of 128 bytes each, for a total of 1040 sectors.
Item: A 1050 will automatically read either density diskette (single or enhanced), but it formats a new diskette according to the format command it receives. In particular, a ! command ($21) causes single-density formatting, while a " command ($22) causes enhanced density.
The software:
Item: DOS 2 is capable of accessing both 810 drives and their double-density equivalents (drives with 40 tracks of 18 sectors of 256 bytes each).
Item: There is an inherent limit of 1024 sectors in DOS 2, since it allows only a 10-bit sector number in the link field of each sector. Also, on a single density diskette, DOS 2 accesses only 719 of the 720 sectors.
Item: The listing of Atari DOS. Actually, this is not a “known” item, and much of what follows is a discussion of what I learned and applied from reading the listing several times.
Armed with these knowns, let’s tackle the unknowns. It seemed to me that the first point to attack was the disparity between what the 1050 was capable of and what DOS 2 would request of it. All of a sudden, DOS 2 must be able to understand three different kinds of disk formats. Question: How can DOS tell what format a particular diskette is?
The answer is to be found in the DOS listing [66: 2213–2222]. During initialization, a status request is made of each drive. When the drive responds, one of the bytes it returns to the computer describes the drive’s type. In particular, the listing makes it clear that a double-density disk has bit 5 ($20) set on. DOS 2 uses this bit to differentiate between 128-byte and 256-byte sectors.
All very well, even assuming that an enhanced mode 1050 returns a zero bit here (which it does, thus properly indicating 128-byte sectors). But what distinguishes an enhanced density diskette? I confess that I obtained the answer to this question through a simple experiment: I simply booted a system with an Indus 1050-compatible drive as D2 and looked at the status value it returned during DOS initialization. Lo and behold, it returned $80. Not surprisingly, the high bit is off in 810 and double-density modes. Voilà.
The second major question to investigate is “How many of the 1050’s sectors can we make DOS 2 utilize?” Well, we already know that 1024 is an upper limit. Is there any other limiting factor? The answer is in the layout of the Volume Table Of Contents (VTOC) under DOS 2. The VTOC contains a single bit for each accessible sector on the disk (a scheme known as a bitmap, though Atari literature often uses VTOC and bitmap interchangeably). If a bit is on (1), the corresponding sector is available. If a bit is off (0), the sector is in use. With eight bits per byte, then, there must be 90 bytes in the bitmap.
DOS 2 allows only a single sector (in this case, 128 bytes) for the VTOC of each diskette. While we could circumvent this restriction, it would require a lot of work, and might cause some secondary problems. (I don’t want to go into this subject more now, but it cost me four to six hours of investigation before I decided against a two-sector VTOC).
In 128 bytes, there are 1024 bits. So it would seem that the limit on number of sectors is indeed 1024. Alas, it is not to be. The description of the VTOC clearly calls out usages for the first six bytes (DOS type, maximum number of sectors, current number of sectors, write-required flag) and reserves the next four. So now we are down to 118 bytes and 944 sectors. Is that our limit?
At first, I was inclined to say it is. But I pored over the listing a couple more times, checked every memory reference that was related, and finally concluded that we could use the four reserved bytes. Which gives us 122 bytes and a final maximum of 976 sectors. Well, that doesn’t seem too bad. We are only 64 sectors away from the theoretical maximum and surely a lot better off than with a limit of 720 sectors.
So this is our plan: Use the upper bit ($80) of the drive status to recognize an enhanced density diskette; allow 975 sectors (DOS 2 always throws away the first possible sector); displace the bitmap in the VTOC by 4 bytes on the low end and lengthen it to 122 bytes.
By the time I had decided on a plan, over half the time I had allotted to this project had elapsed. As I write this, all the allotted time is gone, and I am not done yet. Sounds like a typical software project. Anyway, this month I will tell you of the difficulties I faced. Next month we can decide how well I faced them. In any case, let’s begin the next step.
Before I could start the actual coding of the modifications, I had to find all the places in DOS which would be affected by my scheme. While many parts of DOS are affected by a change in density (from 128- to 256-byte sectors), there are only a few routines which actually care about such things as disk status, where the VTOC’s bitmap is, and how many sectors are available.
Some of the routines I could successfully ignore. For example, when you delete a file and free up its sectors for later use, you must bump the count of free sectors. But if the rest of DOS is working, you don’t have to check for validity of the bumped value. The same thing is true when we allocate a free sector and must decrement the count. And the boot process cares whether we are using 128- or 256-byte sectors, but it doesn’t care how many sectors are on the disk.
But there are several spots which definitely need attention, so let’s discuss them now (next month we discuss the solutions).
And that’s it. Not too bad, right? If only that were true. Remember, our goal here is to patch the standard version of DOS without affecting its normal operations and without requiring a reassembly of the whole thing to make our patches fit. In general, then, the smaller and fewer the patches, the better.
The real problem here is the number of load immediate instructions, used to implement what are now to become invalid assumptions. If these were three-byte instructions (such as loads from a non-zero page memory location), we would have a simple task: Change the values in the locations being loaded.
Since they are load immediate instructions, though, our only choices are to either make large and cumbersome patches (generally JSRs to subroutines which will do the work, but remember that JSR occupies three bytes), use loads from zero page (a neat alternative, but we have no zero page available to us), or to continue to use load immediate.
My choice? Continue to use load immediate. But how? By producing some (shudder at this next phrase, please) self-modifying routines. Remember how I said at the beginning that I sometimes took the path of least resistance? This is one of those sometimes.
The “trick” which allows my scheme to work is relatively simple: Every routine which needs a load immediate changed is only used by DOS 2 after a call has been made to SETUP. Basically, SETUP examines the disk number and drive type and produces various pointers and values in fixed locations for use by other, higher-level routines. What would be more appropriate than for SETUP to also set up the needed values which will be loaded in immediate mode?
And this is, indeed, the plan I tried. At the point where SETUP stores the drive type [92: 5288], I placed a JSR to my patch-it routine. And my patch-it routine used the disk type information to determine which of a pair of immediate values would be used in each of the cases noted above. It looked like it would work.
Except (You knew that was coming, didn’t you?) where do I put the patch? I have discussed this subject before, so let me succinctly say that the only sizable patch area in DOS.SYS is at location $1501, in the gap between DOS.SYS and Mini-DUP (the root of DUP.SYS). There are exactly 63 bytes available there. And my routine was about 85 bytes long.
The story of how I pared my patch down to fit (just barely) will have to wait for next month. Fortunately, it is a short patch. Also fortunately, there are a couple of small patch spaces still floating around in DOS.
Incidentally, if you were looking for the continuation of my notes on how to load saved binary files, keep looking. It turns out that the subject has direct bearing on what we are doing here, so it seemed not inappropriate to postpone it a month (or possibly two).
COMPUTE! ISSUE 49 / JUNE 1984 / PAGE 96
As I write this, I have just returned from the Las Vegas Comdex show.
Comdex stands for “COMputer Dealers’ EXposition,” but it is really a show for those who would sell to the computer dealers. And sell they did. Everything from magic acts to talking robots to sit-down demonstrations (very welcome after walking through literally acres and acres of booths). And, of course, IBM was there in force, occupying an entire building and demonstrating the usual stuff on the PC and, not surprisingly, some me-too-ish software on the PCjr.
The only Atari-compatible hardware products that I saw at Comdex were some disk drives (though I understand that one or two graphics tablets were shown there, also). And that, of course, brings up my next topic.
When you consider the fact that Atari doesn’t even make a double-density disk drive, it’s more than a little surprising and pleasing to discover the amazing degree of compatibility exhibited by the various non-Atari disk drives.
Since OSS provides the disk operating system (DOS XL) which many of the drive manufacturers supply with (or as an option to) their disks, I can’t make judgments as to quality, reliability, etc., without an obvious conflict of interest. I can, however, comment on the features common to all Atari-compatible drives (except those made by Atari itself).
Historically, the reason for the compatibility is the ill-fated Atari 815 drive. For those of you relatively new to the world of Atari, that was the dual, double-density disk drive announced by Atari for delivery in early 1982. Notice the word “was.”
Although never produced in quantity, the 815 survived long enough to cause Atari, Inc., to produce DOS 2.0d (“d” for double), and a few lucky people even have a copy of it. (I’m not lucky.) In fact, even Atari DOS 2.0s can access an 815 style double-density drive for most functions (just don’t try to copy files or duplicate disks).
The folks at Percom Data Corporation, though, didn’t know the 815 was going to die when they started designing their double-density drives. They did, however, want a way to switch from single to double density without having to physically flick a switch. Hence the configuration block was born. Give Percom credit.
Give the other manufacturers credit, also, for recognizing the Percom system as a viable and usable standard. Would you be surprised to find that the same double-density DOS XL diskette works unchanged in drives or controllers from (in alphabetical order) Amdek, Astra, Concorde, Indus, Micro Mainframe, NCT, Percom, Rana, SPI, and Trak? If you are not surprised, you are not aware of the hodgepodge of the CP/M world.
Each of the companies mentioned can tell you of the advantages of their drives or controllers.
A final comment on the configuration block scheme mentioned above. A controller capable of implementing all the options of the configuration block can, in theory, support virtually any size disk drive. At Comdex I saw floppy disk drives with densities over a megabyte. Yum.
I have received more than a little correspondence from readers asking what they can do about the lack of software compatible with their 1200XL (and, now, the 600XL and 800XL). Up until now, my stock answer has been that they should go beat on the heads of the software manufacturers (the ones who didn’t follow Atari’s rules).
Now, though, there is a little relief in sight. Atari has, at long last, made available something known as the Atari Translator Disk. This disk, when booted from any 810-compatible drive into any XL machine with 64K of RAM, will (for all practical purposes) turn your XL computer into a non-XL Atari 800. Virtually all software, including protected games and the like, will then boot and run properly. (Of course, you don’t turn the power off to boot anymore.)
For those who are stuck with incompatible software, this seems like a neat solution. For those who are stuck with incompatible software and no disk drive, this looks like a frustrating solution. Point of interest: I do believe that this software could be loaded via cartridge instead, since one need not turn off the power to change or remove cartridges on an XL machine. Atari, are you listening?
Anyway, if you need the disk, check with your local authorized Atari dealer. If he doesn’t have it, hasn’t heard of it, or is nonexistent, try Atari’s customer service department.
In March, I presented a short program in Atari BASIC which would read a binary object file directly into the memory locations it was originally assembled for (or saved from).
This month, I will start to parallel that listing in machine language. Please understand that this may not be the fastest or easiest way to perform the task. I use the BASIC parallel method as a way of making the program understandable to those who are just beginning to learn machine language.
As a first step, you might look through the listing, noting where the BASIC line equivalents are. They are easy to find. Starting at line 1000, any line number ending in 00 is a comment line which reflects the line in the BASIC program which I presented last month. Note, also, that the line numbers in this listing are 10 times the BASIC line numbers (simply for convenience and readability).
While examining the listing, you probably noted that there seems to be more nonparallel code than otherwise. In truth, this simple pseudo-BASIC program does indeed require a fairly substantial amount of support. The support is in two forms: definitions of variables (including buffers) and I/O subroutines.
You may also have noticed that I assembled the listing in the infamous page 6 memory block. I plead guilty. Actually, in testing this program, I assembled it twice: once at $600, as shown, and once at $6000 (just by changing line 110). I then used the $600 version to read in the $6000 version, and it worked!
Anyway, since I will be giving you complete source code here, I don’t feel too guilty. Obviously, you can change line 110 to anything you wish if you need to stay out of page 6.
There are two other “cheats” in this listing. In line 220, I place NAME at location $580; and, in lines 250 and 270, I place START and ADDR at location $CE. Are these locations truly safe to use? In general, no. If you have been reading my series on self-relocatable code, you know that there are no truly safe locations. But for the purposes of this demonstration, I think we can use them as is, since they are compatible with usage by the Atari Assembler Editor (and MAC/65 and—I believe—AMAC) and Atari BASIC (and BASIC XL but not Microsoft BASIC).
One other comment before we begin analyzing the operation of the listed code. If you wish to use this program as a callable USR routine from Atari BASIC, you need to add this line:
125 PLA; clean up stack for BASIC
Now, onward and downward, into the depths of machine language. I will discuss the lines which I feel are relevant and important by line number.
Line 130. We could have accomplished the same thing by giving a RUN address at the end of the listing, but this gets us started in a visible way.
Line 210. Note the use of the $9B (an AT ASCII RETURN code) to terminate the message. The 0 is for safety and because I am paranoid.
Lines 230, 240, 260. If you consider LOW and HIGH together, they form a 16-bit word. Since QUIT needs to be a word, why not join usage? This is not recommended procedure, but it works if you are careful.
Lines 250, 270. This isn’t surprising if you think about the fact that line 310 in the BASIC code could have been written as FOR START = START TO QUIT, thus eliminating the need for the extra variable, ADDR.
Lines 300–321. These are the same equates you have seen many places, including in the Atari OS listings and Inside Atari DOS though the actual mnemonics may differ slightly.
Lines 550–566. When you get to this routine, it expects the OS channel designator (which is 16 times the Atari BASIC file number) in the X register, the command value in the A register, and the address of the buffer to use in the Y register (low byte) and on the stack (high byte). The routine assumes that you will not be doing I/O which requires over 255 bytes of buffer (a valid assumption for this program, but not for all circumstances).
CMDJOIN sets up the appropriate IOCB and calls CIO to do the real work. It returns the error status to the user in A, Y, and the flags. In this program, only OPEN looks for the error status. (Because PRINT and INPUT to/from channel zero had better work, and if CLOSE fails it’s too late anyhow.)
Lines 500–545. These are the various I/O entry points. Note that they expect the X and Y registers set up as in CMDJOIN. They assume that the high byte of the buffer address is in A and push it on the stack to make room for the command byte. They are simple and effective.
Next month we’ll look at the rest of this listing.
COMPUTE! ISSUE 50 / JULY 1984 / PAGE 104
This month we’ll conclude our exploration of the source code of the program to load a binary file starting with the GET routine presented last month.
Lines 600–619. GET is a special routine for two reasons. First, it assumes a buffer length of zero, thus forcing a single byte transfer into the A register (Atari I/O spec). Second, if the GET fails, it pops a level off the subroutine stack and goes directly to the end-of-file code at line 4000 (BA-SIC’s line 400). This is a crude but effective simulation of the TRAP 400 code in the BASIC version.
For GET to be a general-purpose subroutine, it would have to simply return the status and let the caller do the error trapping.
This routine begins the real work. All object code is reasonably close to its BASIC parallel.
Lines 1200–1204, 1300–1304. Remember the calling requirements for the I/O routines? Channel in X, address in A and Y. Looks easy once you have built the subroutines.
Lines 1400–1407. Same as above. The only extra here is the need to specify a mode for OPEN. Here, we use mode 4 (just as in BASIC) to indicate we will only read the file.
Lines 2400–2405. Since we just stored the A register in HIGH, we test HIGH first by comparing A with 255. If HIGH is equal to 255, then A contains 255 and we can compare it to LOW. A tiny bit sneaky. Did you ever realize that BASIC has to implement THEN this way? By branching around the following code?
Lines 2600–2701. We used LOW and HIGH to get the START address, but we have already moved their contents to START. Now, they won’t be used again until we are through with QUIT, so why not share memory between QUIT, LOW, and HIGH? Again, a little bit sneaky, but not inordinately so.
We could have saved more memory (and code) by doing GETs into the low and high bytes of START directly, but I wanted to keep the code as close as reasonable to the original BASIC.
Lines 3100–3106. See the comments above about START and ADDR.
Lines 3300–3302. Remember, if a zero page location points to a desired memory location, use an offset of zero in the Y register to store, load, add, etc., to or from that location.
Lines 3403–3408. Since we are STEPping by one, we need check only for equality.
Lines 3411–3417. If the FOR loop had used a STEP, we would have had to add it on here. Since the step is implied to be one, we can use this simple two-byte increment.
Line 4103. If this routine is called from DOS or from BASIC, the RTS is all that is needed, thanks to the POP in the GET routine.
As I said, one could write this routine in better ways. The most obvious thought that comes to mind is to replace the FOR loop with a block get of the requisite bytes. Since that would produce significantly faster runtime (for large files, at least), we will make these changes next month.
To do so, though, we will also change the BASIC program to enable it to make a call to do block I/O. So, even if you are not into machine language, watch next month for a method of doing fast memory reads and writes to and from disk.
COMPUTE! ISSUE 51 / AUGUST 1984 / PAGE 102
The assembler listing which accompanies this article is a set of patches to Atari DOS 2.0s. If you own an Atari 1050 drive, these patches will allow you to use it in “enhanced density” mode.
Before we get started with the listing and its explanation, though, let’s look at a new tidbit.
Are you an 800XL owner? Do you have an unprotected diskette which boots a machine language program via an AUTORUN.SYS file? Would you like to avoid pushing the OPTION button? Are you willing to follow a few simple steps to do so?
Your 800XL enables and disables the built-in BASIC by changing the contents of location $D301 (54017). In Atari 400s and 800s, this location is usually used to input the state of joysticks 3 and 4. In an 800XL, this port controls various system hardware configurations.
For example, bit 0 of $D301 controls whether the OS ROM is active or whether you are using the RAM underneath it. And—guess what—bit 1 of $D301 controls whether the builtin BASIC is active or not. Specifically, the following table applies:
Bit 0 = 1 OS ROM enabled 0 OS ROM disabled, RAM enabled Bit 1 = 1 Atari BASIC disabled, RAM enabled 0 Atari BASIC enabled
At least one of the other bits in $D301 is used (to control whether or not the diagnostic ROM is enabled), but the “normal” values for $D301 are either $FF (BASIC disabled) or $FD (BASIC enabled).
So all we need to do is add a couple of instructions to our AUTORUN.SYS file, to select RAM instead of BASIC, and we will no longer have to hold down the OPTION button. For example, we might add:
LDA #$FF STA $D301
And, yet, there is an easier way. Remember, Atari LOAD files may consist of multiple segments, each of which starts with a start address and an end address. The entire file starts with a pair of $FF bytes, but it doesn’t hurt if there are extra $FF header bytes in front of other segments.
So consider: If we specify that we have a LOAD file which starts at location $D301 and ends at location $D301, the DOS file loader will try to load (and thereby store) a single byte at location $D301. This is equivalent to storing a byte via our program.
So simply use the following steps to modify your AUTORUN.SYS to disable the built-in BASIC:
Under Atari DOS 2.0s:
Under OS/A+ or DOS XL:
And that’s it. Your AUTORUN.SYS file should now be ready to use.
Caution! Even though the built-in BASIC is now disabled, HIMEM (the contents of location $2E5) and RAMTOP (contents of location $6A) will still reflect the 40K byte configuration where BASIC is present. If your program pays attention to one or both of these two values, it would also be worth performing the following steps:
These steps will insure that all 48K bytes of accessible RAM are in use by your program. I won’t go into how to accomplish these here and now. Write if you would like me to show how to code those steps in machine language.
A project related to this, which I hope to implement in an upcoming column, would be an “M:” device driver. Once upon a lifetime ago, in this column, I presented such a driver. It used the “excess” memory (between the top of a BASIC program and the bottom of the graphics screen) as a pseudodevice.
I would like to do the same thing again, but this time use the extra memory under the OS ROMs or under the built-in BASIC as a superfast RAM disk. Stay tuned for further developments.
First, I would like to point out that the task of reconfiguring Atari DOS 2.0s for an enhanced density 1050 is difficult. I would also like to note that it is extremely difficult (if not impossible) to finish the task if you have only one drive.
So, may I suggest that you cooperate with a friend and his drive if you have only one of your own. If your friend’s drive is an 810 or a non-Atari drive, it should be set up as drive 1. Your 1050 should be set up as drive 2.
Also, you should use an assembler capable of placing its object code directly in memory. (For example, AMAC—the Atari Macro Assembler—cannot be used for this job.) This is because loading the DOS-modifier code from a disk will use DOS itself, and you are almost guaranteed to run into conflicts. Atari’s Assembler Editor cartridge, the old OSS EASMD, OSS’s MAC/65, and (I believe) SYNASSEMBLER will all work properly (though the syntax for SYNASSEMBLER may vary a bit from what I show here).
You should boot a normal Atari DOS 2.0s disk, making sure that you can access a normal single diskette in drive 2 (at least to the point of making sure you can list its directory). Be sure you have at least two (2) blank or junk disks ready and at hand. Then begin.
Type in the program, as shown herein. You may use automatic line numbering if you wish. Type in just the part from the right of the line numbers. LIST or SAVE the source code to disk and then assemble it. Check it against the listing given here. Do not proceed until you are reasonably sure that you have typed it in and assembled it correctly.
Then change line 1000 to read:
1000 .OPT NOLIST, OBJ
and assemble the code once more.
Voilà! DOS has been patched!
But, because DOS’s DRVTBL has changed format, you must now hit the SYSTEM RESET key. Then give the DOS command from your assembler. Assuming that you get to the DOS menu (and if you don’t, you did something wrong), it would probably be a good idea to immediately format (menu option I) a blank disk in drive 1 and write the DOS files (option H).
Now comes the tricky part. The way we have patched DOS 2.0s, DOS automatically checks each drive at power-on (or SYSTEM RESET) time to find its current configuration (single density, double density, or enhanced density). But the 1050 assumes it is in single-density mode unless you have inserted an enhanced-density diskette. So, up until now, DOS thinks it is working with all single-density disk drives. How do we change its mind?
The easy way: Turn your power off, put your BASIC (or BASIC XL) cartridge into your machine, and turn the power back on, thus booting the disk we just formatted and wrote DOS files to. Insert a blank disk into the second drive (your 1050). From BASIC, give the following command:
XIO 254,#1,0,34,"D2:"
If you are a faithful reader, you will recognize that as the format command, given from BASIC. But the 34 in the next-to-last position is new! That’s right. As we have patched DOS, a nonzero value given in AUX2 is assumed to be the format command value to be sent to the disk drive. The only legal values here are 33 (for single density, à la 810 drives) and 34 (for 1050 enhanced density)!
Now drive 2 contains what we hope is an enhanced-density diskette. Once more, hit SYSTEM RESET so that DOS will recognize the new density. Then give the DOS command from BASIC. Once in DOS, use the H menu option to write the DOS files to drive 2.
If you have performed all these steps correctly, you should now have a bootable enhanced-density diskette in drive 2. You might wish to change your 1050 back to being drive 1 and try to boot from it with this new diskette.
The beauty of this system is that, once you have created this one enhanced-density master, you may make new enhanced-density masters by using just the I and H commands from the DOS menu.
There is, however, one potential problem. How do you copy files from an old single-density disk to a new enhanced-density disk? For now, the only practical way is to borrow a second drive and have one of each type of disk on your system. There may be ways around even this problem. We’ll see.
The patch program given here will also work on all versions of OS/A + and DOS XL from 1.2 to 2.3 (except that it will not patch the DOSXL.SYS versions).
The procedures are almost the same, but it is significantly easier to use a single drive. Try the following if you have only a single disk, on which you boot OS/A+ or DOS XL:
Patches To Atari DOS 2.0s 0000 1000 .OPT LIST, NO OBJ 1010 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1030 ; PATCHES TO ATARI DOS 2.0S 1040 ; 1050 ; THESE PATCHES ALLOW AN ATARI 1050 DRIVE 1060 ; TO UTILIZE ENHANCED DENSITY UNDER 1070 ; DOS 2.0S, TO A MAXIMUM OF 965 FREE SECTORS 1100 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1110 ; 1120 ; EQUATES -- TAKEN FROM THE LISTING OF 1130 ; ATARI DOS AS PUBLISHED IN 1140 ; "INSIDE ATARI DOS" 1150 ; FROM COMPUTE! BOOKS 1160 ; =1311 1170 DRVTBL = $1311 =1301 1180 CURFCB = $1301 =0048 1190 ZSBA = $48 =11DB 1200 DERR1 = $11DB =12FE 1210 DRVTYP = $12FE =0021 1220 DCBCFD = ’! =0302 1230 DCBCMD = $0302 =0045 1240 ZDRVA = $45 =0A4A 1250 NOBURST = $0A4A =0A4C 1260 WRBUR = $0A4C =0D18 1270 XFORMAT = $0D18 =0BD6 1280 XFV = $0BD6 =1372 1290 Z = $1372 =034B 1300 ICAUX2 = $034B =1382 1310 FCBOTC = $1382 1340 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
The rest of this listing will appear in “Insight: Atari” next month.
The reason the procedure works on a single drive is that neither OS/A+ nor DOS XL requires the DUP.SYS file of Atari DOS. The disk initialization can thus be performed entirely from BASIC.
COMPUTE! ISSUE 52 / SEPTEMBER 1984 / PAGE 125
Last month we discussed how to make programs designed for the Atari 400 and 800 load and run automatically on the new XL series without having to hold the option key down. We also looked at a way to make patches into Atari DOS 2.0s to enable it to work with the new enhanced density 1050 disk drive. The procedure is easy, but requires two disk drives. Just type in the source code (the portion printed last month and the continuation found in this issue) using an assembler capable of placing its object code directly in memory. Assemble it after LISTing or SAVEing the source code to disk. After assembling it once, change line number 1000 to read:
1000 .OPT NOLIST, OBJ
and assemble the code once more.
DOS should now be patched. Hit the SYSTEM RESET key and give the DOS command from your assembler. You should now be in the DOS menu (if you’re not, something has gone wrong). Format a new disk using option I and then write the DOS files using option H. This will insure that everything is right and will give you a safe copy of your newly patched DOS.
There’s one more step necessary to finish the procedure. Turn off your computer, put your BASIC (or BASIC XL) cartridge into your machine, and turn the power back on, thus booting the disk that was just formatted. Place a blank diskette into the 1050 drive that you are using as drive 2 and, from BASIC, type the following command:
XIO 254,#1,0,34,"D2:"
Drive 2 should now contain an enhanced-density diskette. Now hit the SYSTEM RESET key so that DOS will recognize the new density. Finally, go into DOS and write the DOS files to the new diskette (D2), using option H from the menu.
If everything has been done properly, drive 2 should now have an enhanced-density diskette containing the patched DOS. Once you have this master completed, creating others is simple and can be done with the I and H options in the DOS menu.
COMPUTE! ISSUE 53 / OCTOBER 1984 / PAGE 142
Let me start my fourth year by discussing the biggest event in Atari history since the introduction of the 800 Home Computer. But first a moment’s reflection on my years with COMPUTE!.
Sometime about three years ago, I was reading COMPUTE!’s “The Readers’ Feedback” column when I noticed a couple of questions about Atari computers which the editorial staff hadn’t answered. And I also noticed a question which a reader had answered incorrectly.
I reacted. I phoned COMPUTE! and, for reasons best known to himself alone, a nice gentleman by the name of Richard Mansfield listened to my ranting and raving. I started to write for COMPUTE!.
Since then, I have written many columns and have covered a wide range of topics. But now I feel that it’s time to change the style of this column. When I started, I intended to answer two or three questions a month and perhaps add a tidbit of my own. Lately, though, I’ve paid less attention to what you, my readers, want and have shown you some exotic but (perhaps for many of you) uninteresting programs, etc.
I am going to try to revive the chitchat style of this column. It will be more fun for me to write like that again and, I hope, more fun for you to read.
As I write this, only a few days have passed since the bombshell exploded: Jack Tramiel bought Atari! I don’t see how I could avoid commenting on this—even if I wanted to.
By the time you read this, some of the things I will speculate on here will have been reduced to the role of mere facts or—equally likely—humorous fiction. Nevertheless, I would like to try to play the crystal ball game. Bear with me, please, as I make some predictions:
Nobody pays a quarter of a billion dollars (even 1991 dollars) for a name alone. If Mr. Tramiel doesn’t produce and sell some (many?) of Atari’s current and/or soon-to-be-current products, he will have bought nothing at all (since the massive layoffs make it obvious that he has little use for the expertise of the people who were Atari).
What products will survive? Probably the 800XL. It’s a good machine and can probably be cost-reduced to be truly competitive with the Commodore 64. It could well have an effective price/performance ratio for well into the next two years.
I’m not so sure about the peripherals. The disk drive, or a version of it, certainly. Printers, of course. The cassette recorder? It’s a piece of junk, and everyone knows it. The much ballyhooed add-on box, with MSDOS, 80-column screen, 128K bytes, and an ice cream freezer? Maybe. But don’t be surprised to see it licensed to a third-party, low-volume manufacturer. It’s too difficult for a lean and mean company to support such a complex product.
What about the game machine side of Atari? Some have suggested that Mr. Tramiel will drop it like a hot potato. Baloney, I say. Why did he buy it then? Was he fooled by Warner and the ex-Atari management? I have heard Jack Tramiel called many things, but “stupid” is not one of them.
I have seen and played with the 7800 “ProSystem.” It is a truly fabulous game machine (and it’s even a fair computer, with 95 percent plus compatible Atari BASIC). Making it 100 percent compatible with the 2600 was a stroke of genius. When I buy one (and I will), I can keep my collection of 20-odd 2600 cartridges. Though I suspect—having seen Xevious and Ballblazer and Rescue on Fractulus and Robotron and… —that they will be little used. Tens of millions, like the 2600? Maybe not. A few million? Definitely yes.
Now what about the supposedly all important, superadvanced, already developed computer that Tramiel and associates are bringing to the game? Well, first of all, I don’t know how far along that machine is. Designed? Almost certainly. Prototypes available? A good probability. Debugged and with software ready? Possible, but I seriously doubt it.
Personally, I expect to see an early prototype shown in January 1985, with “selling” models shown in June, both probably at the Consumer Electronics Shows.
And what will this miracle machine, the savior of Atari as a “name” in the industry, look like? Ah, now there you’ve got me. I am skeptical about reports that it will be a “business” machine: Why buy a game company’s name for such a scheme? But an integrated “noncomputer” such as the Macintosh? Sure! Maybe even “a computer for the rest of us” (Apple’s Macintosh slogan) that is affordable by the rest of us.
Well, how did I score? Or is it still too soon to tell? I am more than a little interested in knowing the outcome.
Several people, led by Lloyd Keller of Palmetto, Florida, wrote me about something I tossed in, offhand, in my June column. While discussing the Atari Translator Disk, I had said, “Of course, you don’t turn the power off to boot anymore.”
And why not? Because, on an 800XL or 1200XL, the Translator Disk software loads into the RAM which is shared with OS (the hidden, bank-selected RAM). It then switches out the ROM completely, leaving you with an Operating System in RAM which is much, much more compatible with the old Atari 800’s OS. Many programs which will not work in XL machines suddenly work just fine.
However, since your OS is now in RAM, you certainly can’t turn off the power in order to boot another disk (for example, a protected game). Similarly, some cartridges insist on the old OS before they will run. You can’t turn off the power to plug them in and still retain the OS in RAM.
Thus, before running, the Translator Disk software allows you to change cartridges or diskettes and then tell it you are ready to do a pseudoboot. That’s all there is to it.
Mr. Keller, however, pointed out that his manuals tell him not once but many times to never change a cartridge with the power on. Well, sometimes manuals tend toward the cautious side.
Point 1: Nobody sticks a cartridge out in plain sight and then designs the electronics so that a three-year-old’s sticky fingers can zap the whole machine by removing it. Point 2: The OS in the XL machines has a complex cartridge-presence checker built in. It checks to see if a cartridge has been inserted or removed every time the OS is called or every 1/60 second.
The action of this checker varies between the 600XL/800XL and the 1200XL. On the former, it causes the machine to “hang” until you hit reset, at which point it does a power-on sequence. The 1200XL simply keeps trying to do a power-on sequence, over and over again, and could lock up as a result.
So my point remains: Someone could and should produce an inexpensive cartridge which would act like the Translator Disk, thus giving cassette-only owners access to a wider range of software.
Shame on all you loyal Atari readers. It took a couple of Commodore 64 owners to bring one of my mistakes to light. A. J. Bryant of Winnipeg, Canada, and David Mackenzie of Bethesda, Maryland, tried the FOR-NEXT nesting test that I presented in my March 1984 column on their 64 machines.
Lo and behold, the program works (it is supposed to fail). And Mr. MacKenzie even asked me if Microsoft knew something we didn’t. Well. I couldn’t take a challenge like that lying down, so I powered up our 64 (yes, we really do have one) and tried it myself. Hmm.
Then I tried it on my trusty 800XL. It worked there also! My face is red. Between the time I developed the test and the time I submitted it for publication by COMPUTE!, I tried to pretty it up. There is a variation on Murphy’s law which is appropriate here: “If it ain’t broke, don’t fix it.”
So Program 1 is the original FOR-NEXT test. It fails on all Atari computers. It fails on Commodore 64s and Applesoft. The normal mode of failure is to issue a NEXT WITHOUT FOR error at line 280.
At first, I was surprised when Apple Integer BASIC passed this test. But I soon discovered why: Integer BASIC doesn’t treat nested FOR loops properly at all. Program 2 is another, simpler test I devised to smoke out BASICs which have this kind of problem, so let’s take a quick look at it.
Line 10 and 20 simply set up a pair of nested loops. But then line 30 starts an outer loop over again (or at least an intelligent BASIC interpreter will think so, since we are reusing I as a loop variable). Thus, line 50 should cause an error, because starting the outer loop over should erase the information about the inner (FOR J) loop. Indeed, on all the BASICs I mentioned except Apple Integer BASIC, it does. With Integer BASIC, though, the error does not occur until line 60. Tch-tch.
If there are any BASICs which pass both these tests, I would like to hear of them. Thanks.
I’ve already started wading through a pile of letters; and, although I obviously can’t promise a response to every one, maybe I’ll try to answer your question or comment next month. See you then.
Program 1: Original FOR-NEXT Test 100 REM IT IS NORMAL FOR THIS PROGRAM TO STOP 101 REM WITH AN ERROR ON LINE 280 110 PRINT "I", "J", "I*J" 120 FOR I = 1 TO 9 130 FOR J = 1 TO 9 140 PROD = I * J 150 IF PROD > 14 THEN 200 160 IF PROD > 10 THEN 190 170 PRINT I, J, PROD 180 NEXT J 190 NEXT I 200 PRINT "J", "I", "J * I" 210 FOR J = 1 TO 9 220 FOR I = 1 TO 9 230 PROD = J * I 240 IF PROD > 14 THEN 300 250 IF PROD > 10 THEN 280 260 PRINT J, I, PROD 270 NEXT I 280 NEXT J 290 STOP 300 STOP
Program 2: FOR-Next Test 2 10 FOR I = 1 TO 3 20 FOR J = 1 TO 3 30 FOR I = 10 TO 12 40 PRINT I, J 50 NEXT J 60 NEXT I
COMPUTE! ISSUE 54 / NOVEMBER 1984 / PAGE 151
After disk drives, probably the most frequently purchased peripheral for personal computer systems is a printer. But buying a printer is a lot harder than buying a disk drive. Usually your choice of drives is limited to the computer manufacturer’s own unit plus a few produced by third-party companies. And despite some slight differences, they all deliver similar performance.
But printers are another story. There are hundreds of printers on the market for personal computers. Most of them can be made to work with your Atari. And they vary widely in terms of price, performance, features, and compatibility.
One of the main differences between printers is their printing speed. Usually this is measured in characters per second, abbreviated cps. By comparing the speed ratings, you can decide whether a certain printer is fast enough for your applications. But recently I discovered how misleading those speed ratings can sometimes be. It all started when those of us at Optimized Systems Software (OSS) began looking around for a new printer.
To begin, let me tell you that we have a rather unique requirement for a printer: We needed a good, fast, reliable printer which we could hook up to any of several computers. And, of course, it had to be compatible with all our software: several languages, four different operating systems, and a couple of word processors.
It is also time for a bit of history. For the last couple of years, our mainstay printer has been a venerable DEC LA-120 Decwriter. This is actually a printing terminal (remember, from the days of mainframe timesharing?) which operates via a serial RS-232-C connection at 120 cps. As reliable as this beast has proven to be, it has a few problems: Its print quality is marginal at best, without even descenders on lowercase letters; because it uses a serial instead of the more standard parallel interface, much software simply will not work with it; although it is rated at 120 cps, it is actually capable of only about 105 to 110 cps when printing typical documents.
At the time, the only other printers we had (or had significant experience with) were a Diablo daisywheel (also serial, at 30 cps), an Atari 825 (rated at 60 cps), and a C. Itoh Prowriter (rated at 120 cps). All had performed adequately (or, in the case of the Prowriter, more than adequately), but all were too slow for our purposes.
And, of course, software compatibility was another big issue. Our primary problem in the past had been that some of our computers transmitted a linefeed after a carriage return (for example, the CP/M based machines), while others (our Atari computers) did not. We were well aware, also, that more problems would be coming as we acquired more software and wanted more capabilities.
For the sake of compatibility then, the first printer that came to mind was the Epson MX-80. Why? Simply because it is used on so many machines with so much software. Yet we immediately rejected the MX-80. Rated at only 80 cps, it is simply way too slow for our applications.
So we started looking for a fast printer which would be largely compatible with the MX-80. To make a long story short, we bought an Epson FX-100, a wide-carriage version of the FX-80. Imagine our surprise when this printer, rated at 160 cps, was only marginally faster than the Prowriter and actually slower than the Decwriter!
It turns out that with few exceptions, the printer speeds published by manufacturers and often faithfully reported by magazines are the maximum instantaneous speeds of which a machine is capable. This instantaneous speed rarely correlates to the actual number of lines a printer will produce in a minute.
What’s more, even those companies which do admit that speed ratings are maximum values employ other claims to suggest that their printer is faster than the competition. For example, many claim that because their printers are bidirectional or logic-seeking, they are faster than the old-fashioned machines which print in only one direction (unidirectional).
Let me describe how the FX-100, for example, prints a typical program listing. First, it receives and prints a line (say, 50 characters), moving the print head from left to right, stopping at the end of the line. Then, it receives the command to print the next line (say, 70 characters). It moves the print head to the seventieth column, stops, advances the paper to the next line, and prints backward from right to left. If the next line is indented (mine often are), it goes through the same sequence of stopping, moving the head, and advancing the paper once again.
But stopping, starting, moving paper, and starting again all take time. A lot of time compared to the actual printing time. Printers like the Prowriter, on the other hand, contain an internal buffer which they use intelligently. After printing a 50-character line, it checks to see where the right end of the next line needs to be and automatically continues to move the head to that position. One stop-and-start sequence eliminated. The results? See for yourself in the following chart, which records the time it took for three different printers to print the same moderate-length program listing:
Printer Rated
Speed (cps)Time
RequiredApprox. Actual
Speed (cps)Decwriter 120 6 min 30 secs 110 Prowriter 120 7 min 45 secs 90 FX-100 160 7 min 30 secs 95
Oh, yes. Did I forget to mention that the Decwriter has no logic-seeking and prints unidirectionally only? That’s a lot of stopping and starting. Sometimes raw power can accomplish what “logic” can’t.
Well, I would like to report that we ran out and bought 30 or 40 different printers and tested them, too, just so I could bring you a full comparison chart. But our budget at OSS won’t stretch that far.
I did, however, go to several dealers and informally time the speed of various printers. Since I had a couple of reference points (the speeds of the Prowriter and FX-100), it wasn’t too hard to get a fair idea of true throughput figures: the printing speeds they could actually sustain.
Then I discovered another trick used by a few manufacturers. Many printers are capable of two or three (or more) character widths or fonts (typically 10, 12, and 17 characters per inch). It seems to me that at least a few printers are rated only for their smallest (and hardest to read) fonts.
Luckily we had an understanding dealer who allowed us to “trade up” our FX-100. And what printer did we then buy? Actually, we ended up buying two.
Because of our need for a printer capable of using the vast library of MX-80-compatible software, we got an Epson MX-100 (simply a wide-carriage MX-80). We have been very happy with it, though I am sure any of several MX-80-compatible printers would have done as well. True, the MX-80 is slow. But its throughput rate seems to be around 50 to 60 cps, which is respectable compared to its rated speed.
Because we needed speed, though, we disregarded MX-80 compatibility for our other new printer, an Okidata 2350 (the model number seems to reflect its retail price). It is rated at over 300 cps and surprised us by performing our little speed test in 1 minute 55 seconds, for a throughput rate of over 360 cps. However, sometimes it gets too hot while printing long listings and stops to wait for the head to cool off. Even so, it probably has a throughput rate of 300 cps or more.
So, did you learn anything from our experiences? I sincerely hope so.
When shopping for a printer, ask to see a demonstration of its speed. Many printers perform better with uniform-length lines (such as those produced by a word processor), so ask to see a program listing also. And make your own time trials.
Judge the print quality for yourself. Ask about replacement ribbon costs. (We found one printer that worked only with carbon ribbons. $$$$! But if you need good print quality, it might be worth it.)
Above all, be certain a particular printer is compatible with your computer and software. Few things are worse than saving $50 on a printer only to find out you have to spend another $100 because your current word processor isn’t compatible with your new printer.
We’ve received a few letters recently on seemingly different subjects, but which all relate to what is obviously some confusion and uncertainty about the Atari XL computers. Let’s address these letters and, at the same time, shed some light on the workings of these little gems.
First, Jacqueline Patton of San Antonio, Texas, asks whether she is “stuck with a problem computer [1200XL] and an unreliable disk drive [Atari 1050].” We’ll discuss the 1200XL’s compatibility problems in a moment. First, a few words about the 1050.
I have not personally observed the 1050 to be any more or less reliable than any other drive on the market. Disk drives, in general, tend to be like automobiles: Sometimes you get one which goes 100,000 miles with no maintenance, and sometimes you get a lemon, but most often you get one which will last a reasonable time with reasonable care and regular checkups. This is not surprising: Disk drives and cars are both mechanical nightmares, subject to extremely close manufacturing tolerances and acute material stresses.
If the 1050 has a problem, it may be simply that it cannot read all of the more strangely protected software disks that are flooding the market. There are antipiracy measures in use today that try the limits of many drives and their controllers. Yet most programs will load fine on any good Atari-compatible drive, including the 1050.
My objections to the 1050 are centered around only one point: Although every other Atari-compatible drive manufacturer has complied with the Percom-standard double-density format (derived in turn from Atari’s defunct double-density 815 drive), only Atari chose to be different. Further, Atari’s method gives you a maximum of 128K bytes per disk. The others get 180K bytes. There is no excuse for this. It results from Atari’s typical blindness when it comes to outside vendors.
All this does not mean the 1050 is no good. It just means that, on a bytes per dollar basis, it is overpriced.
Another letter, from Shahid Ahmal of London, England, was actually a complaint to OSS about the fact that some programs (including our disk-based MAC/65) would not load and run properly on his 800XL. The problem is that these programs require you to remove the BASIC cartridge before booting up—impossible on the 800XL and 600XL, since the BASIC “cartridge” is built into the newer computers as a standard feature. His solution was to write a program which switched off the built-in BASIC, changed RAMTOP, and closed and reopened the screen driver.
Whew! I am impressed. Doing all that in the proper order is not easy. But there really is a much simpler way.
This discussion applies only to disks containing programs which do not use Atari BASIC. Obviously, such things as assemblers, compilers, and utility programs fit this category. Not so obviously, many game disks will not run if Atari BASIC is present. In any case, if you own an 800XL (or, I assume, a 600XL with expanded memory), and the directions for a disk or program tell you to remove your BASIC cartridge, try this:
Turn on power to all devices except the computer. Insert the disk you wish to boot. Push and hold down the OPTION button. Turn on the computer’s power. When the disk starts to load, you can release the OPTION button.
This has the effect of disabling the built-in BASIC. Atari’s manuals tell you all this. But they don’t emphasize enough that you should try this with any disk/program if it otherwise doesn’t work. And they don’t tell you about the OPTION button when used with the Translator Disk. “What’s that?” Glad you asked…
I have mentioned the Atari Translator Disks before in this column, but only part of what I’ll add is repetition.
If you own an Atari 1200XL, 800XL, or expanded 600 XL with a disk drive, run—do not walk—to your nearest Atari users’ group and purchase (usually for about $10) the pair of Atari Translator Disks. (You may still be able to get them from Atari directly.)
The instructions tell you to boot the version A disk first, wait for it to give you a message, insert your otherwise unbootable disk, and push the SELECT key. If that doesn’t work, you are supposed to try the version B disk. (Both disks actually load an old Atari 800-style operating system into your XL machine’s memory, thus hopefully assuring compatibility with programs that rely on the older operating system.)
What the instructions don’t say is that you may also need to hold down the OPTION button. Why? Because otherwise, good old Atari BASIC is still there, messing up the memory address space.
There are, then, no less than six ways to try booting a disk on an XL machine: with or without holding down the OPTION button alone or in combination with either of the two Translator Disks. This sounds like a real pain, but once you find the method that works with a given disk, you can write it down for future reference.
I should note that all of these methods still result in compatibility with only about 97 percent of all software (85 percent of heavily protected software). Is there anything you can do if your favorite piece of software won’t boot using any of these methods? Yes, two things.
First, you can write, phone, telex, or otherwise cajole and threaten the software manufacturer. I have said before, and I am sure I will go hoarse saying again, that I believe the responsibility for the lack of compatibility does not rest with Atari. No other manufacturer has ever produced a series of computers with as many changes and improvements as the XL line and yet maintained as much compatibility as has Atari.
Second, you can try one of the commercial translator programs. I am aware of two at this time: XL BOSS from Allen Macroware and XL FIX from Computer Software Services. I have used neither, so I cannot comment on them. However, I recommend that to avoid unnecessary expense you should certainly seek verification from these manufacturers that the particular software package you want to use will work correctly with their product.
The commercial translator programs do have one interesting bonus: They give your XL computer an extra 4K of memory. Let’s see why.
The original Atari 400 and 800 computers had a 10K operating system and a 2K input/output space. Since the maximum RAM they supported was 48K, that left 4K unused in the total address space of 64K (unless you bought a third-party RAM board—such as those from Mosaic—which placed RAM in this unused space). The empty 4K was located at address $C000 (49152), just above the normal 48K RAM.
When the XL computers arrived, they sported more graphics modes, device downloaders, parallel bus support, self-diagnostics, and more, all of which pushed the size of the operating system up to 14K. Guess where Atari got the extra 4K from? Yep. No more “unused” space.
However, the commercial translators effectively emulate the original 10K operating system, leaving that 4K free again. But since an XL machine has 64K of RAM, the unused space becomes free RAM. If you are using a cartridge-based program (even the built-in Atari BASIC), this isn’t a real big help. The 4K of RAM is still at address $C000, above the cartridge address space. You could install machine language routines here, use it as a buffer for disk I/O or player/missile graphics, or even use it for any graphics screen up to the size of that of GRAPHICS 7. But the average beginner will have a hard time using this space.
On the other hand, programs which don’t use a cartridge don’t have this restriction. For example, if you use one of these translators to load VisiCalc into an XL machine, you’ll gain 4K of valuable spreadsheet space. Try it sometime. It’s easy.
And one more comment before we pause until next month: Since the Atari Translator Disks work much like the commercial translators, it may just be possible to modify them and gain the same 4K of RAM. I have not had the time to investigate this, but if any COMPUTE! readers discover anything in this regard, we’d be happy to hear about it.
COMPUTE! ISSUE 55 / DECEMBER 1984 / PAGE 193
As I promised, this month will be spent answering more letters. Some of the topics I will discuss here have been requested many times; others are unique queries that provide an insight into the workings of your Atari. I think they are all interesting questions.
Before starting on the questions, though, I have a bit of news that can’t wait: Microbits (Albany, Oregon) is currently developing both a parallel floppy disk drive and a hard disk system for the 800XL. Preliminary speed measurements indicate that we may be able to read/write over 40,000 bytes per second to and from the disk. Imagine being able to load any of your favorite games from disk in half a second or so. Presumably, you would use the parallel floppy to back up the hard disk. Since even a five-megabyte disk (small by today’s standards) takes 25 double-density floppies to back up, anything Microbits does to enhance the speed or density of the floppy will be appreciated.
Microbits has not announced any delivery dates yet (in fact, they haven’t even finished development, so they can’t deliver anything), but I think you should ask your local dealer to get all the information he can as soon as he can. Just think of the possibilities for graphics applications (do you realize that you could load five or six graphics mode 15 pictures per second this way? Or how about windows?).
Michael Richardson, of Plattsburgh, New York, used the machine language graphics routines printed in this column in 1982 as the basis for a set of his own routines. He ran up against an unexpected error with the Atari Assembler Editor cartridge. Although he did not provide a complete listing, I will present what I believe is a correct excerpt here:
10 *= $600 ; (or any other good location) 20 DRIVE = FNAME+1 ; see below 30 ; 40 LDA DRIVE ; looks reasonable, doesn't it? … … 99 FNAME .BYTE "D1:ANYNAME.*"
Now that tiny segment of code certainly looks innocuous, doesn’t it? But when you try to assemble it, it gives you an ERROR 13, a “phase” error. Why?
Before answering the question, let’s consider what would happen if we replaced line 40 with:
40 LDA FNAME
Do you know what will happen? Can you guess? Believe it or not, you will not get a phase error from the Assembler Editor cartridge.
Let’s take this step by step. Remember that good old ASMED (if you will pardon my inventing an acronym for ASseMbler EDitor) is a two-pass assembler. On the first pass, ASMED tries to assemble LDA FNAME and discovers that FNAME has not been defined yet. “That’s okay,” says ASMED to itself, “I’ll just assume that FNAME will be defined later as a non-zero page location. I’ll reserve three bytes for this LDA instruction.” Well, lo and not-too-surprisingly behold, FNAME is indeed defined later, and it is indeed not a zero page location. Thus, on the second pass through the source code, ASMED generates a three-byte LDA instruction (both in the listing and in the object code). Pass 1 and pass 2 have agreed on how much code to generate. Voilà, no phase errors.
What happens, though, when ASMED tries to assemble our original line 40, LDA DRIVE? Well, ASMED is smart (just how smart we will see in a moment), but it’s not exactly all-powerful. When it encountered the line DRIVE = FNAME+1, it said to itself, “Aha! FNAME is undefined. But since it is used in an expression, I must give it a value for now. Hmm. Why not give it a value of zero?”
Why not? Because then FNAME + 1 is evaluated by ASMED as 0 + 1, and DRIVE is given a value of 1. ASMED is not smart enough to realize that DRIVE should be considered undefined along with FNAME.
The consequence? During pass 1 of the assembly, ASMED sees LDA DRIVE as being equivalent to LDA $0001, a zero page reference which thus requires only two bytes of memory. But—you saw this coming, didn’t you—by the time ASMED gets to LDA DRIVE on pass 2, FNAME has been defined and so DRIVE gets a value of other than one (presumably $06xx in our little example). “Okay,” says ASMED, “I’ll generate three bytes for the LDA.” Oops! Phase error!
Before discussing the fix for this problem, I would like to point out that many (if not all) of the other assemblers available for the Atari would also produce a phase error here. More interestingly, some (many? I haven’t had a chance to try them all) would probably produce a phase error even on our other example, where we coded LDA FNAME. If so, it is because they treat undefined labels as having a value of zero, and thus reserve space for only a two-byte instruction on pass 1. The situation gets even stickier with forward referenced and/or undefined macro parameters, as implemented in the various macro assemblers available.
Anyway, what is the fix? Well, my favorite rule is simple: Never use a label until after you have defined it. I can’t think of any occasion where this rule will get you in trouble. I can think of lots of ways that ignoring it can cause strange programming problems. My suggestion for the code in question would be to simply rearrange it, thus:
10 *= $600 ; (or any other good location) 20 FNAME .BYTE "D1:ANYNAME.*" 30 DRIVE = FNAME + 1 ; guaranteed to be defined now 40 ; … … 99 LDA DRIVE ;always three bytes now! …
Matthew Ratcliff, of St. Louis, Missouri, sent me a very complete listing of a program he calls “GTIA TEXTWRITER” along with some fairly thorny problems. Without repeating the actual questions, I think I can safely say they should all be lumped into the category of assembling relatively large programs on an Atari computer. Since many people (including Ratcliff) are still using ASMED, let’s begin with a look at how ASMED uses memory.
Much has been written (here and elsewhere) about how Atari BASIC allocates memory, but I can’t remember ever seeing a good description of how ASMED slices up your hard-earned RAM. Shall we rectify that?
First, because ASMED was written primarily by one of the members of the Atari BASIC team (Kathleen O’Brien, and in less than three months), it is not surprising that ASMED shares many of BASIC’s allocation techniques. In fact, those of you familiar with BASIC’s use of the memory pointers at $80 through $92 would be right at home if you looked at ASMED’s source code. There are, however, some major differences.
Just as BASIC has to juggle the several parts of your program (variable name table, the tokenized program, arrays, etc.), so must ASMED find places for its needed components. While you are using just the editor, this task is simple: No tokenizing takes place, no variable name or variable valuable tables are built—just straight-forward expands, contracts, and inserts of your source code lines.
When you assemble, though, ASMED must find a place to put your symbol table (all the labels used in your program and what their values are, etc.). For its own convenience, ASMED simply places the symbol table in memory directly following your source code. Object code is easier: ASMED puts your object code where you tell it to. If you are assembling directly to memory, ASMED puts it in memory exactly where your *= directives tell it to.
I spot some potential trouble with that last part, don’t you? But let’s look at what ASMED can tell us about its usage of memory: Probably the most overlooked tool in the ASMED user’s reach is the SIZE command. This is roughly the equivalent of BASIC’s PRINT FRE(0). When you use SIZE, you are presented with three hexadecimal numbers. The first is the lowest non-zero page RAM being used by ASMED. The second is the current top-of-the-program source code in memory. (Even if you have no program in memory, ASMED has some fixed overhead, so this number never equals the first one.) The third hex number gives you the top of the memory which ASMED will use. Not surprisingly, the first and third numbers are derived from the Atari OS locations LOMEM (at $02E7) and HIMEM (at $02E5).
Let’s take a hypothetical situation (which might really occur if you used a 16K machine with a cassette recorder) where you type SIZE and ASMED responds with:
0700 321C 3C1F
What does this display tell you? It tells me that this person may be in trouble. He has only $0A03 (2563 decimal) bytes left for his symbol table when he assembles this program. Depending on the size and number of his labels, that may or may not be enough space. But that’s only the first problem.
Where is the object code going to go? Aside from poor, overworked page 6 ($0600 to $06FF), that ??? isn’t any memory free (and page 6 probably isn’t big enough to hold the output from this assembly, anyway). What to do? Well, the obvious answer is to assemble your object code directly to the tape recorder. You do that simply by giving the command:
ASM ,,#C:
to ASMED. Then you can use NEW, check memory with SIZE again, and LOAD the object code back in memory, ready to debug it. Not bad. Time-consuming, but it works.
Or does it? Many people complain that after producing an object tape they cannot reload it successfully (usually, they get an ERROR 138, timeout). Why? Simply because ASMED turns on the cassette recorder at the beginning of pass 1, even though it may be a minute or two before pass 2 writes anything to the tape. Also, if you are producing a listing, the time taken to write the tape increases to the point where other start/stop errors are possible. There is no total fix for these problems, but here are some suggestions which might help.
First, do your assembly twice, once for the object code and once for the listing. During the object code assembly, turn off the listing (by using .OPT NOLIST as, say, line 1). Before starting the assembly, zero your tape counter. Then, as the object code is assembled to cassette, listen in (turn up the volume on your television). When you hear the first burst of data being sent to the cassette (near the beginning of pass 2 of the assembly), note the value of the tape counter. Then, to reload the object tape, rewind the tape to about five to ten seconds ahead of the counter value you noted. And that’s about as good as you can do using ASMED with a cassette recorder.
Before going on, I’d like to discuss a point I sidestepped a couple of paragraphs ago. I noted that the SIZE command gave the memory used by ASMED (exclusive of symbol table space). Perhaps not obvious to many first-time users of ASMED is that you may not direct object code (via * = ) to memory anywhere between those first and second numbers. (And you’d better leave a healthy hunk alone above the second number for the symbol table.)
What happens if you don’t follow this rule? Typically, you find that your object code tries to share space with your source. Bye-bye, source. Or, worse, you may find the object code sitting on top of the symbol table. This can cause some extremely bizarre symptoms. I have seen ASMED start spitting out hundreds of errors for a single line when this happened.
Despite the fact that ASMED is one of the most bug-free programs I have ever encountered, it has a few very bad design flaws. And as we just noted, one of them is that it will assemble code right on top of memory it is using for other purposes.
However, for the disk user with 40K or more of RAM, ASMED presents no real problems if used properly. Since both the source code and the object code may be on the disk, the only real limitations are the sizes of the files. Obviously, the object file can be loaded in after giving a NEW command, so it need only fit between the second and third numbers given when the SIZE command is used.
But what about the source file? At first glance, it might appear that your source file is limited to what can be edited in memory. Not so! Albeit tedious, there is a way to assemble very large source files with ASMED. Simply edit the source code in pieces, none larger than ASMED’s buffer space. Then, when all are ready, use the append capability of Atari DOS’s option C to append one file after another to the first piece of the source. (Please do this on a copy of your master disk. It’s very easy to make a mistake and append in the wrong direction.) Now you can assemble this giant source file.
There are, of course, some real disadvantages with doing things this way. The biggest of these is obvious: What happens when you get an assembly error in the middle of the fourth of the appended files? You have to edit that file and then go through the backup and append process all over again. Another problem is simply the speed of ASMED. If you expect to assemble 16K of object code, even without a listing to the printer, you might as well go out to a movie while you wait. A double feature. Finally, ASMED’s extravagant use of zero page memory (leaving you, the programmer, only about 32 bytes) can be a real killer with large programs.
Well, we’ve wandered a little off the original track here, but it’s all been germane to the problems of assembling large programs on your Atari. Is there a general solution to these problems? Several, if you have a disk drive. What are they? Just a nice selection of other assemblers.
ASMED is a usable introduction to machine language programming, but it is (after all) only 8K bytes long, and a lot of features had to be pared to make it fit. So when it begins to grate on your nerves, get rid of it. What do you get instead?
Since my company (OSS) produces MAC/65 (also a cartridge-based assembler, editor, and debugger), any answer I give is bound to be prejudiced. So I will simply tell you to go out and compare the prices, features, and speeds of the various assemblers available. You might, for instance, consult The Book of Atari Software, 1984, from either the Book Company or Addison-Wesley, which describes several assemblers and gives comparison charts. The advantage of getting a second assembler is that you now know what parts of ASMED you did not like, and you can look for assemblers that fix these areas.
The topic heading here does not refer to any secret projects going on behind closed doors. Rather, I have been asked (more times than I can count) about the 16-bit version of the 6502 which has been developed by the Western Design Center (of Mesa, Arizona). I believe it is designated as the 65816, and is purported to be faster than a Motorola 68000 in many operations and capable of addressing 16 megabytes of memory. The question I am asked is fairly obvious: “Can I put this chip in my Atari and address 16 megabytes and make BASIC run faster and …?” The answer is simple: no.
I can’t let an answer like that sit around naked, so let’s see if we can’t flesh it out a bit. First, in order to address 16 megabytes, you have to have 16 megabytes. Have you seen any 800XLs with a lot of spare RAM floating around lately? Further, addressing 16 megabytes means you must have 24 address lines. (The 16 address lines in your Atari computer can access only 64K.) There simply isn’t any place provided on the Atari circuit boards for such an expanded address bus.
Now, at least one version of the 65816 is purported to be pin-compatible with existing 6502s. If this is wrong, I apologize. I admit I am repeating what I have been told. Presuming this to be true, though, it may barely be possible to imagine an expansion box for an 800XL which can properly decode some sort of I/O signal to “bank” in additional RAM. I suspect, though, that the pin-compatible version may be so compatible that it limits you to 64K of memory.
So far, however, this highly hypothetical discussion has assumed that the chip will be compatible enough (with a 6502) to fool the rest of an 800XL’s circuitry. I’m not convinced that this will prove to be true. Why? Because the 65C02 (which, you may or may not recall, is a CMOS version of the 6502 which adds a few—still all 8-bit—instructions and capabilities) does not work in an 800XL. Even though it works great in older Atari 800s.
I am not sure why the 65C02 is incompatible with the 800XL, but I have been told it is because Atari started using a custom version of the 6502 in its newer machines. (The story is that the newer CPU is the same one found in the 2600 game machines, and it has one or two pins used differently.) In any case, the problems with the 65C02 cause me to doubt that the 65816 will enjoy a better fate.
Last, let us assume that you really can plunk a 65816 down into the middle of your 800XL. Will it do you any good? Not unless you are a heavyweight in machine language. Compatible means just that: It executes all standard 8-bit 6502 instructions in the same old way. And where are you going to get any of the new 16-bit instructions from? I dunno. It is extremely doubtful that any major software vendor will be able to justify the expense of developing programs which use the 65816 in an Atari, since using the chip involves doing nasty things to your computer that very, very few users are willing to try.
And there you have it. I hope I am wrong about much of the above, solely for my own personal satisfaction with such a 16-bit machine. But—sigh—I am probably mostly right. (But what if … nah … it couldn’t happen.)