What is USR?

Len Golding explains ...

 

Issue 11

Sep/Oct 84

Next Article >>

<< Prev Article

 

 

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