All About The Commodore USR Command
John L. Darling
Have you wondered how to use BASIC'S USR command? This article explores this useful feature with examples for every Commodore computer.
This introduction to the USR function will form a basis for more complex applications. We'll explore passing double precision integers between BASIC and machine language.
Here's how the USR function works. Both the USR and SYS commands are like the BASIC GOSUB command. Instead of transferring control of a BASIC program to a BASIC subroutine, USR and SYS cause control to go to a machine language subroutine.
But unlike the SYS function, the USR function has the additional capability of transferring numbers or information to or from a machine language subroutine.
The USR command format is n = USR(v), where n is any variable name, and v is any variable whose value is to be transferred into the machine language subroutine. Upon return to BASIC, the machine language subroutine will place the newly computed value into the variable n. The transfer of values and information between BASIC and machine language is accomplished via the floating-point accumulator (FAC). The FAC is five consecutive bytes in memory that are used for storing floating-point numbers (numbers which can have decimal points in them). Address information for the machine language subroutine is specified in locations 1 and 2 (785 and 786 in the Commodore 64), and is stored in standard LBHB (Low Byte, High Byte) format.
For example, the command nv = USR(ov) in a BASIC program would first transfer the value of ov to the FAC. Then the program branches to the machine language subroutine whose starting address is stored in locations 1 and 2 (785 and 786 in the 64). Before leaving the machine language subroutine, the programmer stores the newly computed value in the FAC, and issues an RTS command (ReTurn from Subroutine). Upon reentering BASIC, the value in the FAC is held by the variable nv, and the BASIC program continues from where it left off.
The only thing preventing you from taking full advantage of the USR function is the conversion of the floating point data in the FAC. The number must first be in a format your machine language program can use, and then the computed value must be reformatted back into the FAC for the return trip to your BASIC program. The secret lies in knowing the location of two important subroutines in the ROM code. One of these subroutines converts the contents of the FAC into a double precision integer stored at $61 and $62 on the PET, or $64 and $65 on the VIC and 64. The second subroutine converts the double precision integer in the A and Y registers to a floating point number in the FAC.
These two subroutines are easy to use. Note: These examples are for PET Upgrade BASIC. Refer to the table reference for the appropriate locations on your computer.
1. Convert the FAC to a double precision integer. JSR $DBA7 ;CONVERT FAC TO AN INTEGER IN $61 AND $62 LDA $61 ;MSB OF INTEGER LDY $62 ;LSB OF INTEGER
The A and Y registers contain the converted integer value of T in the BASIC equation, S = USR(T). It can be saved and used by your machine language program when needed.
When you need to transfer a value from the machine language program back to BASIC, the double precision integer must be placed in the FAC in the proper format. The following code will accomplish this nicely.
2. Convert a double precision integer in A, Y to FAC. LDA $... ;MSB OF INTEGER LDY $... ;LSB OF INTEGER JSR $D26D ;CONVERT INTEGER IN A,Y TO FAC RTS ;RETURN TO BASIC
In the equation, S = USR(T), S will contain the converted value provided by the machine language program. These two ROM subroutines should be sufficient for most user applications.
To illustrate the use of these subroutines, let's use the USR function to simulate the PEEK instruction. This allows us to evaluate the subroutines by comparing the test results with the results of a known instruction. The particular example chosen is useful only as a learning tool.
If you wish to try the example on your computer, use the following procedure:
For PET/CBM with Upgrade or 4.0 BASIC:
- Access the Monitor by SYS 1024.
- Display the proper block of memory by typing .M 033C 0352.
- Type in the code from Program 1 or 2 as appropriate for your PET. Do a monitor save on it by typing .S"@0:USR.M",08,033C,0352 for disk or .S"@0:USR.M",01,033C,0352 for tape.
- Enter the BASIC Program 5 and SAVE it as USR.B.
- RUN the BASIC program.
The only reason for saving these programs is to avoid reentering the data in case of an unrecoverable crash due to a typing error.
If it is necessary to load the programs again, always load the machine language program first, and then the BASIC program. Otherwise, the BASIC address pointers will be incorrect and your program will not run properly.
For the VIC-20 and Commodore 64:
- Type in Program 3 or 4 as appropriate, then add the lines from Program 5 (64 owners note line 120). The numbers in the DATA statements comprise a machine language program and must be typed in correctly.
- Before RUNning the program, SAVE it to tape or disk.
- RUN the program.
If you are curious about what the machine language does, here is a disassembly of the Upgrade BASIC version.
$033C 20 A7 DB START JSR $DBA7 ;FACTOINTEGER IN $61, $62 $033F A5 61 LDA $61 ;T-MSB $0341 85 FC STA $FC ;TEMP SAVE MSB $0343 A5 62 LDA $62 ;T-LSB $0345 85 FB STA $FB ;TEMP SAVE LSB $0347 A0 00 LDY #$00 ;INDIR INDEX $0349 B1 FB LDA ($FB),Y ;DATAAT$FB,FC ADDR $034B A8 TAY ;S-LSB $034C A9 00 LDA #$00 ;S-MSB = 0 $034E 20 6D D2 JSR $D26D ;CONVERT A,Y TO FAC $0351 60 RTS ;RETURNTO BASIC $0352 .END
Making USR Work
Now let's look at all seven steps that are necessary to make the USR function work. These steps, as used in the example, will be explained in detail for the Upgrade BASIC version. Again, you can refer to the table for the appropriate locations on your particular computer.
• STEP 1: POKE the USR Jump Address in Locations 1 and 2.
The first step is to put the machine language start address in locations $01 and $02. The least significant address byte (LSB) is stored in location 1, and the most significant address byte (MSB) is stored in location 2. In the example, the machine language start address is located at $033C. Therefore, locations $00 through $02 should contain the following 6502 code:
ADDR. CODE MNEMONIC $0000 4C 3C 03 JMP $033C
In BASIC, address values must be stated in decimal. The conversion for the LSB address byte $3C is 3 * 16 + 12 = 60 and the MSB address byte $03 is 0 * 16 + 3 = 3, providing the POKEd values in line 110. It is not necessary to POKE location 0 ($310 on the 64) as it was initialized to the proper value when power was applied.
• STEP 2: Determine T in S = USR(T).
In your BASIC program, establish a value for T in the equation S = USR(T). To use the ROM subroutines provided in this article, T must be an integer with a value between 0 and 65535. This is the full range of 2-byte integers and in hex is equivalent to $0000 through $FFFF. In the example, lines 130-150 are used to input and test an integer number between 0 and 65535.
• STEP 3: Execute S = USR(T).
When S = USR(T) is executed, it is equivalent to SYS(828). In both cases, control is transferred from BASIC to your machine language program. The USR function differs from the SYS command in that the FAC can be used to pass real data to and from the machine language program. Note that 828 = Loc 1 + 256 × Loc 2 (828 = 60 + 3 × 256).
• STEP 4: Convert FAC to Positive Integer.
While in your machine language program, convert the value in the FAC to a two-byte integer. The JSR to $DBA7 at $033C converts the FAC to a double precision integer whose MSB is located in $61 and LSB is located in $62.
• STEP 5: Execute ML Program.
In the example, the instructions located from $033F - $034A are used to get the value that we wish to pass to BASIC.
• STEP 6: Convert Positive Integer to FAC and Exit.
The instructions in $034B through $0351 put the integer value in the "A" and "Y" registers. Since the simulated PEEK value is really only a single precision integer, the MSB is set to zero. The JSR to $D26D will convert the values in the A and Y registers to floating point and place them in the FAC. Finally, the RTS will return control to BASIC.
• STEP 7: Verify S in S = USR(T).
The real variable S, in the equation S = USR(T), will now be assigned the value placed in the FAC by your machine language program. Lines 170 and 180 in Program 5 display both the value of S and the actual PEEK value to verify that the simulation is correct.
That's all there is to it. When you break it down, step by step, it's not that difficult. Perhaps the USR function will now find a place in your programming arsenal.
Floating Accumulator Locations For PET/CBM, VIC, And 64
|Computer||Loc. of FAC to Integer Routine||Result Left in Location||Loc. Of Integer to FAC Routine||USR Vector Location|
Program 1: PET Upgrade BASIC Version
033C 20 A7 DB A5 61 85 PC A5 0344 62 85 FB A0 00 Bl FB A8 034C A9 00 20 6D D2 60 00 FF
Program 2: PET 4.0 BASIC Version
033C 20 Dl CD A5 61 85 FC A5 0344 62 85 FB A0 00 Bl FB A8 034C A9 00 20 BC C4 60 00 FF
Program 3: vic-20 version
10 FOR A = 828 TO 849 : READ D : POKE A, D : NEXT 20 DATA 32, 155, 220, 165, 100, 133, 252, 165 30 DATA 101, 133, 251, 160, 0, 177, 251, 168 40 DATA 169, 0, 32, 145, 211, 96
An Explanation Of LBHB
The Low Byte, High Byte (LBHB) data storage format is a method many microcomputers use to store large numbers. Because a byte can hold a number no larger than 255, two or more consecutive bytes are needed to represent numbers larger than 255. The LBHB format involves a method in which numbers are broken down, then stored in memory with the least significant byte (LSB) first, followed by the most significant byte (MSB).
A number between 256 and 65535 is stored in RAM memory using two consecutive bytes. The second byte (the most significant byte) is derived by dividing the original number by 256, and then storing the integer (no fractions) value into the MSB. The remainder of this division is then stored in the first or least significant byte. Thus you use the following formula for reading LBHB numbers in memory:
number = LSB + (MSB * 256)
For example, let's say that you wanted to USR to address 828 (the cassette buffer in most Commodore machines). You would need to put 828 into addresses 1 and 2 and it would have to be in this LBHB format.
Here's how it's done:
1) Divide 828 by 256 and store the resulting integer byte 2.
828/256 = integer 3
2) Store the remainder of this division in byte one:
828 - (256 * 3) = 60
The Two Methods
To automatically store numbers into and read numbers from memory using the LBHB format, use these two formulas:
To read a LBHB number, where N = number:
N = byte 1 +(256 * byte 2)
To store a LBHB number, where N = number to be stored:
NN = INT(N / 256) : POKE byte 1, N - (NN * 256): POKE byte 2, NN
If you're new to computing, please read "How To Type COMPUTE!'s Programs" and "A Beginner's Guide To Typing In Programs."
Program 4: Commodore 64 Version
10 FOR A = 828 TO 849 : READ D : POKE A, D : NEXT 20 DATA 32, 155, 188, 165, 100, 133, 252, 165 30 DATA 101, 133, 251, 160, 0, 177, 251, 168 40 DATA 169, 0, 32, 145, 179, 96
Program 5: USR Demonstration
100 REM SAVE BEFORE RUNNING 110 POKE 1, 60 : POKE 2, 3 : REM JMP $033C 120 REM FOR C-64, USE POKE 785, 60 : POKE 786, 3 130 PRINT "SIMULATED PEEK" : PRINT 140 PRINT "INPUT AN ADDRESS BETWEEN 0 AND ˜ 65535" 150 INPUT T : IF T < 0 OR T > 65535 OR INT(T) <> T THEN 140 160 S = USR(T) : REM SYS 828 ($003C) 170 PRINT S" = PEEK("T")" , PEEK(T) : PRINT 180 GOTO 140