Back Up Machine Language Programs With BASIC

Ed Stewart

This fairly technical article shows you how to back up cassette-based machine language programs. If that's not a priority for you, it's still worthwhile reading. The author explains IRGs (interrecord gaps) on the Atari and a good bit more.

If you have any machine language programs on cassette tape, you may be painfully aware of what a "BOOT ERROR" message means to you. If you haven't yet experienced the anguish of a non-readable machine language tape, then read on and avoid future pain. For those of you who are not masochistic and who, like me, have lost a tape or two, take heart, for you can now save a backup copy in your wine cellar tape vault. The program described in this article allows you to make your own private copy of any Atari machine language program. It is almost as easy as CLOAD/CSAVE and is well worth your time to incorporate into your program library.

When I began developing this BASIC program, I thought it would be easy as GET/PUT and would be a trivial program requiring perhaps 30 minutes at the outside to develop. Wrong. After about 16 hours of work over three weeks I came up with this solution. I could not have done it without the Atari Operating System source listing. My final version of the program ended up with two machine language subroutines filling and emptying a very long string variable while tricking the Operating System Cassette Handler into doing all the I/O for me.

The basic reason why I had to resort to machine language was very simple – BASIC was too slow to do the job. Each block of data stored on a cassette tape is 128 bytes in length. All I had to do was read a block of data from the tape, transfer all 128 bytes to my string, and read the next block of data in before the tape ground to a halt. Each block of data on the tape is separated by a gap called an interrecord gap (IRG). There are two kinds – short ones and long ones. The short IRG's are used when you can dispose of the 128-byte data block quickly and request the next one while the record motor is still on. If the recorder stops between blocks on a tape that has short IRG's, then when it starts back up again it will begin reading from the tape just a little bit beyond where the data really is. The result is usually error code 143 – Serial Bus Data Frame Checksum Error. Long IRG's, on the other hand, are long enough to permit the tape to come to a complete stop and start again without any loss of data or error codes. Long IRG's are used, therefore, when the 128-byte data block in the cassette buffer cannot be used very fast, for example, when you use the GET commands in BASIC. Machine language programs are stored on tape with short IRG's. By the time you can issue 128 GET commands and transfer those 128 bytes into a string, the tape has stopped. The 129th requested GET will recognize an empty cassette buffer and cause a new data block to be read from the tape, but it will be read too late – the motor stopped with a short IRG tape and bingo – error 143.

The solution was to request a data block from tape with a GET command, empty the cassette buffer into my string with a machine language subroutine and make the machine think that its cassette buffer was empty so that a subsequent GET would cause another data block to be read into the buffer. This process was repeated until the EOF condition was obtained on the tape. Then the string had to be written back to tape, using another machine language program to empty the string into the cassette buffer while using the PUT command to cause the actual tape write to occur. If this seems a bit complex to you, perhaps the following diagram will help.

READ SIDEWRITE SIDE
OpenOpen
Basic "GET"empty string to cassette
Move cassette buffer to stringmark cassette buffer full
mark cassette buffer emptyBasic "PUT"
EOF – CLOSEEOF – CLOSE

The only limitation this program has is that the program size you may copy is limited by the size of the string A$. The size of the string A$ is limited by the size of your available RAM and is derived dynamically based upon your RAM size. A string cannot exceed 32K and so A$ is limited to 32K. In other words, a program greater than 32K cannot be copied.

Now that I have showered you with all the technicalities of this little program, let's see what it looks like line by line.

PROGRAM. Back Up Machine Language Programs With BASIC.


1 REM BACKUP TAPE UTILITY FOR MACHINE
2 REM LANGUAGE PROGRAM OR TO BACKUP
3 REM ANY 600 BAUD TAPE WITH SHORT
4 REM IRG'S FOR THAT MATTER
5 REM
6 REM AUTHOR ED STEWART
10 NO = 0 : N1 = 1 : N2 = 2 : N256 = 256 : GRAPHICS N2 + 16 : REM SET LOW MEMORY GRAPHICS MODE
20 Z = PEEK(742) * N256 + PEEK(741) - PEEK(145) * N256 + PEEK(144) - 1500 : IF Z > 32767 THEN Z = 32767
24 DIM A$(Z) : REM SET STRING LENGTH
30 A$(1) = "{,}" : A$(Z) = "{,}" : A$(2) = A$
34 REM INITIALIZE STRING IN 30
40 POKE 203, ADR(A$) - (INT(ADR(A$)/N256) * N256) : POKE 204, INT(ADR(A$)/N256) : REM POKE STRADR FOR M.L. ROUTINE
60 FOR I = 1536 TO 1565 : READ A : POKE I, A : NEXT I : REM POKE IN M.L. ROUTINE
70 TRAP 200 : REM SET TRAP FOR EOF
74 ? #6; "INSERT INPUT TAPE" : ? #6; "press any key to{4 SPACES} begin"
80 OPEN #N1, 4, 255, "C" : CNT = NO : REM OPEN INPUT FILE
90 FOR I = NO TO Z STEP 128 : REM SET INPUT LOOP COUNTER
100 GET #1, B : CNT = CNT + 128 : REM FILL CASSETTE BUFFER FROM TAPE
120 X = USR (1536) : REM MOVE BUFFER TO STRING AND MARK BUFFER EMPTY
140 NEXT I : ? "NOT ENOUGH RAM TO COPY TAPE" : END
200 IF PEEK(195) = 136 THEN CLOSE #N1 : GRAPHICS N2 + 16 : ? #6; "INSERT OUTPUT TAPE"
202 ? #6; "press any key to {5 SPACES} begin" : GOTO 210
204 ? "ERROR - ";PEEK(195) : END
210 RESTORE 10000 : REM SETUP FOR 2ND M.L. PROGRAM
220 FOR I = 1536 TO 1566
230 READ B : POKE I, B : NEXT I : REM POKE IN 2ND PROGRAM
234 POKE 203, ADR(A$) - (INT(ADR(A$)/N256) * N256) : POKE 204, INT(ADR(A$)/N256) : REM SET UP STRING ADD FOR 2ND PGM
240 OPEN #N1, 8, 255, "C" : REM OPEN OUTPUT TAPE
260 FOR I = NO TO CNT STEP 128 : REM SETUP OUTTAPE LOOP COUNTER
262 X = USR(1536) : REM EMPTY STRING TO CASSETTE BUFFER AND MARK BUFFER FULL
270 Z = ASC(A$(I + 128)) : PUT #N1, Z : REM PUT LAST BYTE IN BUFFER AND WRITE TO TAPE
300 NEXT I
320 CLOSE #N1 : GRAPHICS N2 + 16 : ? #6; "THAT'S ALL FOLKS" : REM SAY DONE OK NOW
400 FOR I = NO TO 800 : NEXT I : RUN : REM MAKE MORE OUTPUT TAPES IF DESIRED
9000 DATA 104, 174, 138, 2, 134, 61, 160, 0, 162, 0, 185, 0, 4, 129, 203, 200, 230, 203, 208, 2, 230, 20 4, 196, 61, 240, 3, 76, 10, 6, 96
10000 DATA 104, 169, 128, 133, 61, 160, 0, 162, 0, 161, 203, 153, 0, 4, 200, 230, 203, 208, 2, 230, 20 4, 196, 61, 240, 3, 76, 9, 6, 198, 61, 96

Return to Table of Contents | Previous Section | Next Section