We are pleased to welcome Larry Isaacs and his new column, "64 Explorer," to the pages of COMPUTE!. Larry has extensive experience in programming with 6502-based machines.
To get things started, here's the first of a two-part article on a little understood but important feature of BASIC, the STATUS variable.
This is the first of a two-part series dealing with a feature of BASIC which is not too well documented, the STATUS variable. It is used to detect the success or failure of input/output operations. I came to this realization while writing a disk copy program. At one point I thought I had the program fully operational. I was surprised to discover that the program was copying all but the last byte in the file, causing the copy to be one byte shorter than the original.
Fortunately I was able to quickly determine that the problem was not in my programming. The program worked exactly as I intended it to. Instead, my error was that I made an assumption concerning the STATUS variable which turned out to be incorrect. An error arising from an erroneous assumption can be a very tough one to find. Everything looks right, and doesn't work.
Tracking Down A Mistake
After discovering my error, I reread the documentation to find where I might have over-looked something relating to my mistake. (By documentation, I mean the Commodore 64 Programmer's Reference Guide and the 1541 Floppy Disk User's Manual.) After a thorough reading, I wasn't able to find anything saying that my assumption wasn't valid. I even found an example program in the 1541 User's Guide which makes the same mistake I did. Fortunately, I had some previous experience which led me to quickly suspect and correct my assumption. It can be quite frustrating if you don't have that experience, and have to acquire it the hard way. Since the STATUS variable is not documented very thoroughly, and is crucial to proper I/O (input/output) communications, perhaps we should try to discover the necessary information ourselves. The all-important question we need to answer is: "What does the STATUS variable really tell us?"
Before starting our investigation, a little introduction to the STATUS variable may prove helpful. The purpose of the STATUS variable is to provide an indication of the completion status of the last input/output operation. It is supposed to indicate, among other things, when an error occurs in the I/O operation, and when the end of the data is reached while reading a file. The end of the data in a file is more commonly called end-of-file, abbreviated EOF. My invalid assumption involved this EOF indication.
The STATUS variable acts like a normal variable in most respects. It may be used in an expression just like any other variable, and may be abbreviated ST. However, it differs from other variables in that you can't give it a value. It only returns a value. Here are a few examples showing how the ST variable might be correctly used:
1050 SS = ST : REM SAVE THE STATUS IN VARIABLE SS 2000 IF ST< >0 GOTO 9999 3110 EOI = ST AND 64 : REM GET EOI STATUS
An example of an incorrect use of the ST variable would be:
100 ST = ST - 64
where the statement attempts to assign a value to ST. This results in a SYNTAX ERROR.
STATUS Can Catch Errors
The value returned by the STATUS variable is used to detect if anything unusual happened during the last I/O operation. The unusual things that may occur will vary depending on which device is involved. The Reference Guide describes the meaning of the STATUS variable only for the cassette and serial bus devices, such as the 1541 disk drive. Therefore, we will concentrate on these. In either case, the value returned by the ST variable will be a signed byte. To keep things simple, think of this as a byte of memory holding a number which can range from -128 to +127. This differs from an unsigned byte which can hold values from 0 to 255.
Instead of getting into a discussion of bits and binary numbers, we will interpret the unsigned byte as being the sum of a unique combination of numbers from the group: 1, 2, 4, 8, 16, 32, 64, -128. An important restriction is that these numbers may appear in the sum only once. The presence of one of these numbers in the sum equivalent to the value of ST will indicate the presence of a particular condition. Let's explain this a little further.
You can find which numbers make up the sum by repeating the following steps, using the ST value as the initial remainder: (1) Subtract the next number in the group from the current remainder, starting with -128 and proceeding toward 1. Subtracting -128 is the same as adding + 128. (2) If the result is positive and less than 128, include the subtracted number in the sum and use the result as a new remainder. (3) If the result is negative or greater than or equal to 128, don't include the subtracted number in the sum, and then keep the old remainder. When the remainder becomes zero, you will have the numbers which make up the sum. Here are a couple of examples to show how it's done.
Using STATUS With Tape
Since more readers are likely to have the cassette unit instead of a disk drive, we will first investigate the STATUS variable as it relates to cassette. To begin, let's take a look at what the STATUS variable indicates when accessing the cassette.
|16||UNRECOVERABLE READ ERROR|
|64||END OF FILE|
|-128||END OF TAPE|
(Note: This information can be found in the Commodore 64 Programmer's Reference Guide on page 85.)
Since the END OF FILE indication was the one I had trouble with, let's begin there. The primary question we want to answer is "When will the STATUS variable indicate we are at EOF?" Will EOF be indicated as the last byte of the file is read, or will EOF be indicated when you try to read one byte beyond the last byte?
To answer this, all we need is a simple little test program. This test program should first write a cassette file containing a few bytes. Then it should instruct us to rewind the cassette. Finally, it should read the cassette file, displaying each byte and the ST variable as each byte is read. Here is a test program to do this, which will be called "TEST 1":
100 OPEN 1, 1, 2, "TEST" 110 PRINT#1, "ABC"; : CLOSE 1 200 PRINT "REWIND THE CASSETTE." 210 PRINT "PRESS RETURN WHEN READY." 220 INPUT Z$ 300 OPEN 1, 1, 0, "TEST" 310 FOR I = 1 TO 5 320 GET#1, Z$ : PRINT I, Z$, ASC(Z$ + CHR$(0)), S T 330 NEXT I : CLOSE 1
As you can see, lines 100-110 write the file, 200-220 ask you to rewind the cassette, and 300-330 read the file. The 2 in the OPEN command in line 100 specifies that the file is being opened for writing with an END OF TAPE marker to be written when the file is closed. You might note that the program reads five bytes from the file, though only three bytes are written. This is done so we might answer another question, namely, "What happens if you read past the end of the file?" Executing TEST 1 will cause the following to appear on the display screen:
PRESS RECORD & PLAY ON TAPE OK REWIND THE CASSETTE. PRESS RETURN WHEN READY. ? PRESS PLAY ON TAPE OK
This includes all of the prompts which occur during program execution. Since it is the displayed data which will answer our questions, we'll limit ourselves to that in the tests which follow.
The first column of the displayed data shows a count of the bytes in the file. The second column displays the character, with the third column giving the numeric value (called the ASCII value) of the character. The last column shows the value of the ST variable after the GET command which fetched the character.
From this data, we are now prepared to answer the first question. The EOF indication appears with the reading of the last byte of the cassette file.
Concerning the second question, we now have some test results to examine. First, note that the test program read the two additional bytes with no apparent ill effects – no errors occurred, etc. Second, we can see the EOF indication went off once the next byte was read. And third, notice the first byte following the last one we wrote (i.e., the C) is suspiciously a zero. Since a zero byte doesn't correspond to a displayable character, there is a blank space in the second column where a character would have been. Zero bytes are often used when there is need of a byte which marks the end of something. Thus, it is not too surprising to find one here. But if a zero byte is being used to mark the end of a file, what is going to happen if a zero byte is written as part of the data in the file? To answer this question, let's modify the TEST 1 program to make another test program. Make the following changes to TEST 1 to make "TEST 2":
100 PRINT # 1, "A" ; CHR$(0) ; "C" ; : CLOSE 1
As you can see, TEST 2 will write a zero byte in place of the "B" written by TEST 1. Executing the TEST 2 program displays the following results:
Ah ha! This time we got two EOFs. Since it's not possible for a file to have two ends, one must conclude that what is given as an EOF indication, strictly speaking, doesn't indicate EOF. Instead it is a "next byte is a zero" indication. Provided you do not write any zeros as part of your data, then the zero byte at the end (which is added automatically by the 64) might properly provide the EOF indication. However, if you should accidentally read past the end of your data, you could be in trouble. It doesn't appear that the GET # command can tell if you've passed the end of the file.
The Cassette Buffer
At this point, you might be wondering where the data is coming from once you read past the end of the file. The answer to this is fairly simple. Whenever data is read or written to a cassette file, it will be done in groups of 191 bytes, called blocks. There is an area of memory reserved to hold this block, called the cassette tape I/O buffer. In the case of writing a cassette file, the data is stored in the cassette buffer until the buffer becomes full (that is, contains 191 bytes). At this point the BASIC program will temporarily stop executing while the cassette motor is turned on and the buffer contents written to the cassette.
Now the buffer is considered empty, and execution of the BASIC program resumes. When the buffer becomes full again, another block is written. When the program closes the cassette file, a zero byte is placed following the most recent byte stored in the buffer and the entire buffer written as the last block in the file. This means there may be bytes following the zero byte which are left over from the previous buffer full.
A similar process occurs for reading a cassette file. Blocks are read from the cassette into the cassette buffer, and then read from the buffer until it becomes empty. As you might have guessed by now, the bytes we are reading after the end of the file are the leftover bytes in the buffer.
So far, our two test programs wrote only three bytes to the file. This implies that our cassette file contains only one block of data. Since our test programs wrote an END OF TAPE marker following the file, it might be interesting to find out what will happen if we read past the one block. To find out, make the following change to the TEST 2 program to make "TEST 3":
310 FOR 1 = 1 TO 196
This reads five bytes past the one block of data in the cassette file. Executing the TEST 3 program displays the following as the last five lines.
|194||[< C >]||252||0|
The [< C >] is the character which would be entered by pressing the COMMODORE key plus C. The first four numbers turn out to be the beginning and ending addresses of the cassette I/O buffer. From this I would assume that the END OF TAPE marker is simply an additional 191-byte block written after the file. It seems to have no terminating effect while data is being read. I suspect that the END OF TAPE marker has an effect only if encountered while the 64 is searching for a file with a specific name.
Well, that's about all the experimenting we have space for in this column. In next month's column we will continue with a few more experiments on the cassette, then look into the disk drive.