USR is probably the least
well-documented function in ATARI BASIC, yet it is potentially one of
the most powerful. This introduction is in three parts. First we look at
what USR is and the syntax it uses. This is for BASIC programmers who
have come across it in program listings and just want a feel for what it
does. The second part looks at the way USR works and outlines the
general principles of inserting machine-code subroutines into BASIC. If
you are not into machine-code and don't intend to start writing your own
routines, you can comfortably skip this bit. Finally, we look at some
examples you can experiment with, including a routine for copying the
ROM character set into RAM at lightning speed.
Let's start with the
simplest form a USR statement can take. This looks something like
X=USR(1536). In English, this means 'Stop executing BASIC for a moment,
go execute the machine-code routines which start at address 1536 and put
the resulting number into variable X'. In the majority of cases you
won't give two hoots what the value of X turns out to be, what is
important is the execution of the subroutine along the way. If it is
designed to move a player about on the screen, your only concern is
whether the movement works. If it is a scrolling routine, then it is the
actual scrolling that counts. Any hypothetical number generated at the
end of the routine would probably have little relevance to the real
world anyway and you would hardly ever use it for anything. So why
bother assigning a variable to it?
The answer is that USR is
not a command like GOSUB or POKE. It is a function, like PEEK or INT, so
we can't simply type USR(1536), we have to give it a command to work
with. Theoretically, we could use any command that works with a number.
TRAP or RESTORE would do, provided we knew that the number would never
exceed 32767 (the maximum allowed with these commands). PRINT would also
work but would mess up your screen display. The most convenient command
is LET using the format 'LET X=' or, more simply 'X=', since this does
not place restrictions on the number following it and has no discernable
effect on program execution.
You are not stuck with X
as a variable name of course. Some programmers prefer statements like
MOVE=USR(1536) or SCROLL=USR(1536) to
give a clue about the subroutine's purpose. Similarly, the number in
brackets need not be 1536 although that is a
common one since it is the first location of an area (page 6) specially
reserved for things like machine-code subroutines. It need not even be a
'raw' number. Variable names or expressions (like Z*3+5) are equally
acceptable, provided they evaluate to the correct starting address. The
thing to remember is that, however many numbers appear in the brackets,
the first one is always the place where the machine-code routine
starts.
Now a machine-code
program is simply a long list of numbers, each representing either a
command or an item of data, depending on its position. You can, if you
like, see what the routine looks like by finding the place in your BASIC
listing where the numbers are put into memory, starting at the address
given in the USR statement. There are numerous ways of getting a list of
numbers into RAM. Very large routines might be loaded from cassette or
disk directly into the chosen memory area, using something known as a
'direct CIO call', but it is unlikely you will encounter this method in
public domain BASIC programs. A very common technique is to POKE the
numbers one at a time into RAM, using READ and DATA statements. A third
approach is to DIMension a string to the length of the machine code
routine, then store the list of numbers in that string as ATASCII
symbols. In such cases, the USR statement will take the form of X=USR(ADR(A$))
and you will find A$ written out somewhere in the listing, looking like
a meaningless jumble of characters and symbols. This method saves both
space and time, since it eliminates the need for a machine-code loading
program, but it is extremely vulnerable to typing errors and a single
mistake can crash the system. One final, and little used, approach is to
put very short machine code routines into the USR statement itself,
either as extra numbers or ATASCII symbols. De Re Atari gives the
example X=USR(ADR("hhh/*/LV/d"),16) but I have never seen it
used anywhere else and most subroutines will be too long to encode in
this fashion. My personal preference is for the POKE, READ and DATA
technique. This is much kinder for anyone who has to copy the program
from a listing and makes debugging a lot easier.
Let's now look at the other numbers you
might find in a USR statement's brackets. How about X=USR(1536, 100, 20,
3000, PLR1, MEMTOP-10)? These extra numbers are known as 'parameters' to
make the point that they are not addresses.
They are just ordinary numbers which will be used somewhere in the
machine-code routine called by that USR statement. A parameter can be a
real number, a variable name or an expression, the only restriction is
that it must evaluate to a number between 0 and 65536. It might indicate
which joystick the routine should read, or which player to move, or
which colour register to use, or where in memory to find some data, or
where to store a result - almost anything in fact. It is even possible,
by using variable names, to pass the result of an earlier calculation
carried out in BASIC, like how much memory there is left at any given
time, or where to put an explosion on the screen. A USR statement can
contain up to 255 parameters, but you are not likely to encounter more
than half a dozen or so.
Discovering what the
parameters mean in any given instance is a thankless task unless the
programmer has deliberately made it easy for you. Sometimes he or she
will have used variable names whose function can be identified from a
close inspection of the BASIC listing. Alternatively, there may be a REM
statement close by explaining all. If neither of these applies, there is
normally no easy way of discovering what the parameters mean, or how the
machine code routine uses them. Just type them in and trust the
programmer!
Now on to the second
part, how USR works. I'll assume that if you are reading this section
you know about converting decimal numbers to 2 byte integers and how a
LIFO stack operates. If not skip the next couple of paragraphs. Better
still, get hold of a decent book on machine code and find out!
When a USR call is made, the
following things happen:
a) The processor notes
where it is in the BASIC program, and pushes this location onto the
stack for later use as a return address.
b) Any parameters
passed are converted into 2 byte integers and pushed onto the stack,
low byte first.
c) A one-byte value
containing the number of parameters passed (even if it is 0) is pushed
onto the stack.
d) the machine code
routine is executed.
e) On encountering the
final RTS instruction, the top two bytes are pulled off the stack and
used as the return address. All
being well, this transfers control back to BASIC, at the next
statement after the USR call.
Note that I say 'all being
well'. A number of things can go wrong if we're not careful. First of
all, there is that byte mentioned at c), sitting on top of the stack
ready to foul things up. Unless we get rid of it, the processor will
think it is part of the return address and, when the final RTS is
encountered, will bounce off into the lower reaches of operating system
RAM instead of returning to BASIC. Consequently it is a good idea to do
a PLA right at the start of your routine to be sure you don't forget it.
For exactly the same reason, all parameters have to be pulled off the
stack before the final RTS. They can be left there until you need them
in your routine of course, but newcomers to machine-code programming may
find it safer to retrieve them at the start of the routine. They can
always be stored in a less critical location until you need them.
Remember that all parameters
are converted into 2 byte integers, even if their value could be
contained in a single byte, so to retrieve a one byte parameter, you
have to do two PLAs and discard the high byte. Also, don't forget that
parameters come back off the stack with their high byte first. This can
be a bit confusing if you are used to the conventional 'low/high' order
of storing 2 byte numbers. Lastly, do make sure that your routine ends
with an RTS. I know this sounds obvious, but it is easy to forget,
especially if the routine uses a lot of JSRs (e.g. accessing ROM
routines).
On now to the third section,
where we get down to some practical examples. Here is the simplest
machine code routine I can think of
PLA
;Get rid of the number of parameters byte
LDA
20
STA
710 ;Store 20 in the address controlling screen colour
RTS
;Return to BASIC
In decimal form, this routine translates
to 104,169,20,141,198,2,96. Before we can do a USR call though, these
numbers have to be put into some safe area of memory. Let's use address
1536 onwards. Here is my favourite way of
loading machine code subroutines into RAM, though it is not necessarily
the best.
10
X=0:RESTORE 40
20
READ D:IF D=-1 THEN 100
30
POKE 1536+X,D:X=X+1:GOTO 20
40
DATA 104,169,20,141,198,2,96,-1
100
X=USR(1536)
As with any machine-code
routine, SAVE it before you RUN it, since the slightest error in the
DATA line could lock up your system. When you RUN it, you should find
that the screen turns orange. Okay, it's a trivial example, you could
have achieved the same effect by POKE 710,20, but at least it's a start.
Notice the 96 at the end of the routine. This is the RTS instruction
which we need to get back into BASIC.
How about an example with
a parameter? This slightly modified routine allows you to specify the
screen colour within the USR statement.
PLA
;Discard the number of parameters byte
PLA
;Discard the parameters
high byte
PLA
;Get the parameters low byte
STA
710 ;Store it in the colour register
RTS
;Return to BASIC
Subroutine writers note:
even though this particular parameter can only be a one-byte number (you
can't put more than 255 into a colour register), the USR call will still
push two bytes onto the stack, so we have to pull the high byte off and
throw it away. To load this more flexible subroutine, change lines 40
and 100 of the BASIC loader program above to read:
40
DATA 104,104,104,141,198,2,96,-1
100
X=USR(1536,90)
This time the screen
should turn red, but you can choose whatever colour you like, simply by
altering the 50 in the brackets.
Finally (and at long
last) something useful. Anyone who has used redefined character sets in
their programs knows that, before any redefinition can be done, the
character set has to be copied from ROM into RAM. This takes about 10 to
15 seconds in BASIC, depending on the method used. The following
machine-code routine does it in the
blink of an eye! Alter the BASIC loader program as follows
40
DATA 104,104,133,204,104,133,203,169,224
50
DATA 133,206,160,0,132,205,162,4
60
DATA 177,205,145,203,136,208,249,230
70
DATA 204,230,206,202,208,242,96,-1
100
X=USR(1536,10240)
The parameter 10240 is,
in this case, the address where the copied character set will start. It
need not be 10240 of course, you might prefer to calculate where the top
of your useable memory is and put the new character set up there,
remembering to leave enough space for the screen memory and display list
and 1024 bytes for the characters. Try changing line 100 to read
100
START=256*(PEEK(106)-20):X=USR(1536,START)
When you run it for the
first time, the routine does not look particularly fast. This is because
of the time taken to load the numbers into memory, but, once there, the
routine can be called as many times as you like and will execute very
quickly indeed. To see it at full speed, RUN it once then type GOTO 100.
The real benefits arrive, of course, when you use the routine more than
once. You can copy the character set three or four times over (to
different locations of course) in a couple of seconds. This is useful if
you want several incarnations of your redefined characters to get
animation effects of the kinds used in Space Invaders. That subject,
however, deserves another article all of its own.
In conclusion, let's look
at some of the reasons for going to all this bother. There are two main
ones. Firstly machine-code can do some things BASIC can't manage at all,
and secondly, it executes up to 200 times faster. On the other hand,
machine-code takes longer to write, is far more exacting and makes
complicated tasks out of some things BASIC finds easy. The USR function
gives us the best of both worlds. We can use BASIC to do all the things
which would be very tedious in machine-code, like complex arithmetic, or
setting up screen displays, or using peripherals and employ machine-code
for tasks which need to be executed at high speed like moving player/
missiles vertically, or reading light pens, or fine scrolling.
All in all, it's well worth
making friends with USR.
top