Calling Assembly Language
Programs from BASIC
Sometimes it's hard to decide whether to write a program in BASIC or in assembly language. But it isn't always necessary to make that choice. In many cases, you can combine the simplicity of BASIC with the speed and versatility of assembly language by simply writing assembly language routines that can be called from BASIC. In this chapter, that's what you'll be learning to do.
The first two programs we'll be working with are the ones that were introduced in Chapter 7: the programs called The Visitor and Response. Before we can call these two programs from BASIC, however, we'll have to make a couple of changes in the way the two programs are written. First we'll have to delete the infinite loop at the end of each program, and replace it with an RTS instruction, so that each program will return to BASIC when it's done instead of looping around endlessly. We'll also have to add a PLA instruction at the beginning of each program so that the programs won't mess up the stack when they're called from BASIC. Later on in this chapter, we'll explain exactly why these PLA instructions are needed. If you've just finished Chapter 7, and still have your computer on, you can load The Visitor and Response programs into your computer and make the necessary changes in them now.
Starting with The Visitor
Let's start with the program called The Visitor. With your assembler active and in its EDIT mode, load the source code of The Visitor into your computer and type LIST. This is what you should see:
10 ; 20 ; THE VISITOR 30 ; 35 TXTBUF=$5041 40 OPNSCR=$5003 50 PRNTLN=$5031 55 BUFLEN=40 60 FILLCH=$20 70 ; 80 *=$0600 90 ; 0100 TEXT .BYTE $54,$41,$4B,$45,$20,$40,$45,$20 0110 .BYTE $54,$4F,$20,$59,$4F,$55,$52,$20 0120 .BYTE $4C,$45,$41,$44,$45,$52,$21 0130 ; 0140 VIZTOR 0150 JSR FILL 0160 LDX #0 0170 LOOP LDA TEXT,X 0180 STA TXTBUF,X 0190 INX 0200 CPX #23 0210 BNE LOOP 0220 JSR OPNSCR 0230 JSR PRNTLN 0240 INFIN JMP INFIN 1300 FILL 1310 LDA #FILLCH 1320 LDX #BUFLEN 1330 START 1340 DEX 1350 STA TXTBUF,X 1360 BNE START 1370 RTS
Now you can amend the program so it can be called from BASIC. First, insert a line containing a PLA instruction by type
Then change the infinite loop in line 240 to an RTS instruction by typing
When that's done, I suggest that you change the name of the program to VISITOR.SR2 to distinguish it from the original program. To do that, just type
20 ; VISITOR.SR2
and hit the carriage return.
When you've made all of those changes in the VISITOR.SRC program, type LIST again and here's what you should see:
10 ; 20 ; VISITOR.SRC2 30 ; 35 TXTBUF=$5041 40 OPNSCR=$5003 50 PRNTLN=$5031 55 BUFLEN=40 60 FILLCH=$20 70 ; 80 *=$0600 90 ; 0100 TEXT .BYTE $54, $41,$4B,$45,$20,$40,$45,$20 0110 .BYTE $54,$4F,$20,$59,$4F,$55,$52,$20 0120 .BYTE $4C,$45,$41,$44,$45,$52,$21 0130 ; 0140 VIZTOR 0145 PLA 0150 JSR FILL 0160 LDX #0 0170 LOOP LDA TEXT,X 0180 STA TXTBUF,X 0190 INX 0200 CPX #23 0210 BNE LOOP 0220 JSR OPNSCR 0230 JSR PRNTLN 0240 RTS 1300 FILL 1310 LDA #FILLCH 1320 LDX #BUFLEN 1330 START 1340 DEX 1350 STA TXTBUF,X 1360 BNE START 1370 RTS
Saving your Amended Program
When you've made all of the necessary changes in your Visitor program, you can save it on a disk once again - in both its source code and object code versions - under the file names VISITOR.SR2 and VISITOR.OB2. Then you'll be ready to amend the other program introduced in Chapter 7 - Response - so that it, too, can be called from BASIC.
To fix up RESPONSE.SRC so that it is accessible from BASIC, just load its source code into your computer and make the same three kinds of line changes that you made in your Visitor program. When you've made the necessary changes, this is how Response should look:
10 ; 20 ; RESPONSE 30 ; 40 TXTBUF=$5041 50 OPNSCR=$5003 60 PRNTLN=$5031 65 BUFLEN=40 70 FILLCH=$20 75 ; 80 EOL=$9B 90 ; 0100 *=$0650 0110 ; 0120 TEXT .BYTE "I AM the leader, you fool!",EOL 0130 ; 0140 RSPONS 0145 PLA 0150 JSR FILL 0160 LDX #0 0170 LOOP 0180 LDA TEXT,X 0190 STA TXTBUF,X 0200 CMP #$9B 0210 BEQ FINI 0220 INX 0230 JMP LOOP 0240 FINI 0250 JSR OPNSCR 0260 JSR PRNTLN 0270 RTS 1300 FILL 1310 LDA #FILLCH 1320 LDX #BUFLEN 1330 START 1340 DEX 1350 STA TXTBUF,X 1360 BNE START 1370 RTS
When you've made the necessary changes in the Response program, you can save it in its new version under the file name RESPONSE.SR2 and RESPONSE.OB2. Then we'll be ready to call both The Visitor program and the Response program from BASIC. To do that, you'll need a BASIC cartridge for you computer if it's an Atari 400, 800 or 1200XL. (You won't need a BASIC cartridge if you have a late model Atari, since the newer Atari models have BASIC built-in.) Anyway, when you have your computer up and running again, in BASIC now, and with your data disk in our disk drive, the first thing you'll have to do is call up the Atari DOS menu. When your BASIC interpreter's READY prompt appears, select menu option L (BINARY LOAD) and load VISITOR .OB2, RESPONSE.OB2 and PRNTSC.OB2 into your computer's memory.
When you have all three programs loaded, type B to return control of your computer to your BASIC interpreter. Then, when your BASIC interpreter's READY prompt appears on your screen, you can type in this BASIC command:
If you've typed and assembled your VISITOR.SR2 program just the way we did, and if its object code is now stored in your computer's memory, then it should run as soon as you type X=USR(1559) and hit a carriage return, since its starting address is $0617, or 1559 in decimal notation.
Similarly, you can run RESPONSE.OB2 by simply typing
and hitting your carriage return.
Now that you've called two programs from BASIC, we're ready to talk about how you did it: specifically, how the Atari BASIC USR function works, and how it's used in assembly language programming.
The USR Function
Machine language programs, as we have just observed, are called from BASIC with a special function called a USR statement. The USR function can be written in two ways: either with or without one or more optional arguments. A call that does not include arguments is written using the format we used to call our Visitor and Response programs:
When a call is written using this format, the number in parentheses equates to the starting address (expressed as a decimal number) of the machine language program being called. When the machine language program ends, control of the computer will be returned to BASIC. If a program is running when the USR function is used, the program will resume at the first instruction following the USR function when control is return to BASIC.
Works Like "GOSUB"
In this respect, a USR statement works just like an ordinary GOSUB instruction. Like a subroutine written in BASIC, a machine language program called from BASIC must end with a return instruction. In BASIC, the return instruction is, logically enough, RETURN. In assembly language, the return instruction is RTS, which stands for "ReTurn from Subroutine." A call, in which arguments are used, looks like this
or like this:
When arguments are used in a USR function, each argument can be any value that equates to a 16-bit number. Machine language operations can be performed on the values referred to in the arguments, and the results of those operations can be passed back to BASIC when the machine language operations are completed. In this way, operations that take place at machine language speed can be used in BASIC programs. Whether arguments are used in a USR function or not, the machine language program called by the USR function can also return a 16-bit value to BASIC when it passed control back to a BASIC program.
Returning to BASIC
To return a value to BASIC when a machine language program has ended, all you have to do is store the value that is to be returned in two special 8-bit memory locations before control is returned to BASIC. Those two locations are $D4 and $D5 (212 and 213 in decimal notation). When a value to be returned to BASIC is stored in these two locations, it should be stored with the low byte in $D4 and the high byte in $D5. When control returns to BASIC, the 16-bit value in those two locations will automatically be converted into a decimal value ranging from 0 to 65535. Then that value will be returned to BASIC as the value of the variable in the USR statement, for example, the "X" in X=USR(1536,X,Y).
How the USR Function Works
When a BASIC program encounters a USR statement, the memory address of the current instruction in the BASIC program being run is placed on top of the hardware stack. Next an 8-bit number, the number of arguments that appear in the USR statement, is the pushed onto the stack. If there are no arguments in the USR statement, then a zero is placed on top of the stack. When a USR statement calls a machine language subroutine, the machine language subroutine's return address is always covered up on the stack by another number and therefore, even if that number is a zero, it must be cleared from the stack before control can be returned to BASIC.
Clearing the Stack
And that is why the first mnemonic in most machine language programs designed to be called from BASIC is a "clear the stack" instruction: PLA.
More Stack Operations
If arguments are included in a USR function, then more operations involving the stack take place when a machine language program is called. Before the number of arguments is placed on the stack, the arguments themselves are pushed onto the stack. Then, when the machine language program begins, the arguments can be removed from the stack and process by the machine language program in whatever way the programmer wishes. Finally, when the machine language program ends and control of the computer is passed back to BASIC, the results of any machine language operations that may have been carried out using the arguments in the USR statement can be stored in memory addresses $D4 and $D5. The values that have been stored in this pair of location can then be returned to BASIC as he value of the variable in the original USR statement.
A Sample Program
That may sound like a mouthful, but another sample program that you can type into your computer and call from BASIC right now should clarify what we're getting at. Type the following program into you computer.
A 16-BIT ADDITION PROGRAM
10 ; 20 NUM1 = $CB 30 NUM2 = $CE 40 SUM = $D4 50 ; 60 *=$0600 70 ; 90 CLD 0100 PLA;CLEAER # OF ARGS. FROM ACC. 0105 PLA 0110 STA NUM1+1;HIGH BYTE OF 1ST ARG. 0120 PLA 0130 STA NUM1;LOW BYTE OF 1ST ARG. 0140 PLA 0150 STA NUM2+1;HIGH BYTE OF 2ND ARG. 0160 PLA 0170 STA NUM2;LOW BYTE OF 2ND ARG. 0180 ; 0190 CLC 0210 LDA NUM1;LOW BYTE OF NUM1 0220 ADC NUM2;LOW BYTE OF NUM2 0230 STA SUM;LOW BYTE OF SUM 0240 LDA NUM1+1;HIGH BYTE OF NUM1 0250 ADC NUM2+1;HIGH BYTE OF NUM2 0260 STA SUM+1;HIGH BYTE OF SUM 0270 RTS
When you've finished typing the program, you can assemble it and save it in both its source code and object code versions. The suggested file names for the listings are "ADD16B.SRC" and "ADD16B.OBJ".
An Adding Machine Program
When you have your programs saved, you can remove your assembler from your computer, and type in this BASIC program:
THE WORLD'S MOST EXPENSIVE ADDING MACHINE
10 GRAPHICS 0:PRINT :PRINT "WORLD'S MOST EXPENSIVE ADDING MACHINE" 20 PRINT :PRINT "X="; 30 INPUT X 40 PRINT "Y="; 50 INPUT Y 60 SUM=USR(1536,X,Y) 70 PRINT "X+Y=";SUM 80 PRINT 90 GOTO 20
When you have finished typing in the program, you can save it under the file name "ADD16B:BAS". That will complete our preparations for calling our newest addition program from BASIC. You can now call your ADD16B.OBJ from BASIC the same way you called your Response program. Just call up your DOS menu and load the binary file ADD16B.OBJ. Type "B" to get back into your BASIC editing mode, load your ADD16B.BAS file, and type "RUN". You can then start using "The World's Most Expensive Adding Machine."
As you can see by running a few numbers through your new adding machine, it can perform 16-bit addition. Although we haven't covered 16-bit addition in this book yet, you can probably figure out how the program works with very little trouble.
First, in lines 20 through 40, the program reserves memory space for three 16-bit numbers, two numbers that are added, and their sum. The two numbers to be added are labeled NUM1 and NUM2, and the address where their sum will be store is labeled SUM. In line 100, the program clears the hardware stack with a PLA instruction. That gets rid of the 8-bit "number of arguments" value that our BAIC program has placed on top of the stack - the 16-bit numbers that have been included in the USR statement in our BASIC program. These are the two numbers to be added, and our machine language program now places them in the two pairs of 8-bit memory locations that have been labeled NUM1 and NUM2.
The Heart of the Program
Now we've come to the main part of our assembly language program. In line 200, the program clears the carry flag, as every good addition program should. Then the actual addition begins.
The program adds the low bytes of NUM1 and NUM2, and stores the result of this calculation into the low byte of SUM. It then adds the high bytes of NUM1 and NUM2 (along with a carry, if there is one) and stores the result of this calculation into the high byte of SUM.
That's all there is to it. As it turns out, and this is no accident, the memory address that have been reserved for the value SUM are $D4 and $D5, the two special memory locations that are always returned to BASIC as the value of the variable in a USR command. That's quite a mouthful, but it really isn't hard to understand. What it means is that our machine language program has now solved the equation it was presented with when we handed it the USR instruction in our BASIC program. That USR statement, as you may remember, looked like this:
the values have now been assigned to all of its variables.
The "X" and "Y" in the equations were assigned values when you typed them in during the BASIC part of the program. Then, when control of your computer was transferred to machine language, the values of X and Y were converted to 8-bit numbers and pushed onto the hardware stack. In the machine language program which you wrote, the values of X and Y were pulled from the stack. They were then stored in two pairs of 8-bit memory locations (labeled NUM1 and NUM2, to avoid any confusion with the 6502's X and Y registers). Next, the values of X and Y (now called NUM1 and NUM2) were added. Their sum was stored in $D4 and $D5. When control was returned to BASIC, your BASIC interpreter converted the value in $D4 and $D5 into a 16-bit value and assigned that value to the BASIC variable SUM, the variable used in the USR statement that called the machine language program. Then, as instructed in the BASIC program you wrote, your BASIC interpreter printed the value stored in the BASIC variable sum on your video screen.
This was quite a complex series of operations, as most sequences of machine language operations are. Unfortunately, we still haven't written a very useful assembly language addition program. While the adding machine we have just created may be very expensive, it isn't a very useful one in real world applications. It can add 16-bit numbers and print out 16-bit results. That's a definite improvement over the 8-bit addition program we created a few chapters back, but it still has some serious deficiencies. It can't handle numbers or results that are longer than 16 bits. It can't work with floating point decimal numbers, or with signed numbers. If you type in a number that's too big for it to handle, it won't let you know, it will simply "roll over" past zero and add numbers without any carrying, and give incorrect results.
Obviously, we have not yet managed to write an addition program that will work as well as a good addition program should. We haven't even looked at any subtraction, multiplication or division programs. Very shortly, though, we shall. We'll also be discussing many other topics including signed numbers, BCD numbers, bit manipulations and more in Chapter 10, Assembly Language Math. Before we get to Chapter 10, however, there's another bit of ground to cover in Programming Bit by Bit, the topic of Chapter 9.
Return to Table of Contents | Previous Chapter | Next Chapter