Making BIOS Calls from BASIC on the Geneva Many people are aware that you can use BIOS operations to give BASIC programs some of the power and speed that was formerly limited to machine language programs. Few, however, are aware of the techniques required to do this. In the example program below, you will see how a BIOS routine (ADCVRT) can make a BASIC program more powerful and read the internal and external voltages. Learning how to use BIOS calls can be very useful for new programmers. It allows them the ease and simplicity of writing in BASIC and then reverting to the more difficult machine code only when specialty problems of speed or power arise. The end result can be powerful programs written with ease. Some knowledge of machine code and operating systems is required. General Procedure You begin by writing your program in BASIC, leaving the machine portion undone. When you are satisfied that all the BASIC keyboard I/O routines, menus, etc. are in working order, you can develop the machine code portions that will give you the additional power. Developing the Machine Language Portion Here is the sequence of tasks the machine language portion must accomplish to handle a BIOS call and pass the results back to BASIC: 1. Discover the warm boot address. 2. Add a certain number to it to call the particular BIOS routine. Each BIOS routine you call requires a different number to be added to it to address that routine. 3. Store this updated address to memory. The stored address (which is the sum of the WBOOT address and an added offset) equals the calling address of the desired BIOS routine. 4. Call the desired BIOS routine (in the example below it reads voltage through the A/D port). 5. Return to the BASIC program where the data retrieved from the called routine is converted to meaningful numbers to be displayed on the screen. The machine language program that does this is shown below. Once the machine language program is developed, a couple more steps remain. The machine language op codes must be be converted to decimal codes. Finally, the converted machine language (now in decimal format) must be inserted into upper memory (usually the USER BIOS area) with POKE statements assisted by READ/DATA statements. If you choose the USER BIOS area to write your machine code portion in, you must set aside enough room with the CONFIG program. Since the machine program is less than one page of 256 bytes in the example program, the USER BIOS is located at hex address EB00. If the routine were more than one page, the starting address would move down one page for each page of code. For example, a two page routine would have to start at hex address EA00. WARNING: Do not attempt to measure A.C. voltages of any magnitude or D.C. voltages greater than 2 volts. You can seriously damage the Geneva if you try to do this. Sample Program that Reads the Geneva Battery and External Voltages 10 REM This is a program to read internal battery voltages and external D.C. voltages up to 2 volts. 20 REM It is written in BASIC, but it calls machine language routines in the USER BIOS. 30 CLS 40 IF PEEK(&HF00B)<>1 THEN PRINT "USER BIOS SIZE IS NOT 1 BLOCK-- USE CONFIG PROGRAM TO CORRECT THIS: SET TO 1 PAGE.":END 50 INPUT "DO YOU WANT TO READ INTERNAL BATTERY OR EXTERNAL VOLTAGES(B/E)";V$ 60 IF V$="E" THEN GOTO 300 65 IF V$="B" THEN 90 70 PRINT "USE CAPITALS -- ENTER B OR E" 80 GOTO 50 90 REM *** SECTION TO DEFINE INITIAL VALUES *** 100 DEFINT A-J,L-Y 110 K=5.7/63 'DIVIDE 5.7 V INTO 64 PARTS FOR INTERNAL 120 Z=2/64 130 ADRS=&HEB00 'USER BIOS AREA 140 T=0 150 REM *** SECTION TO POKE MACHINE CODE DATA TO DISK ***: 160 FOR I=&HEB00 TO &HEB17: 'THIS IS THE USER BIOS AREA 170 READ A 180 T=T+A 190 POKE I,A 200 NEXT I 210 IF T<>2362 THEN PRINT "ERROR IN DATA LINES!!!!":END 220 IF V$="E" THEN POKE &HEB0B,0 230 REM *** SECTION TO CALL ROUTINE AND CONVERT DATA *** 240 CALL ADRS 250 X=PEEK(&HEB17) 'GET THE STORED NUMBER THAT = VOLTAGE NUM 260 PRINT "NUMBER RETURNED FROM A TO D PORT=" X 270 IF V$="E" THEN PRINT X*Z "VOLTS" ELSE PRINT X*K "VOLTS" 280 END 290 REM EXTERNAL VOLTAGE SECTION 300 CLS:PRINT "NEVER EXCEED 2 VOLTS - USE D.C. VOLTAGES ONLY****" 310 PRINT "ACCURACY LIMITED TO 6 BITS (=1 PART IN 64)" 320 PRINT:PRINT "HIT ANY KEY WHEN READY TO PROCEED" 330 INPUT Z$ 340 GOTO 90 350 DATA 42,1,0,125,198,111,111,34,13,235,14,3 360 DATA 205,114,231,31,31,230,63,50,23,235,201,61 Detailed Look at the Machine Code Portion EB00 LHLD 0001 2A,01,00 42,1,0 EB03 MOV A,L 7D 125 EB04 ADI 6F C6,6F 198,111 EB06 MOV L,A 6F 111 EB07 SHLD EB0D 22,0D,EB 34,13,235 EB0A MVI C,03 0E,03 14,3, EB0C CALL EA72 CD,72,EA 205,114,231 EB0F RAR 1F 31 EB10 RAR 1F 31 EB11 ANI 3F E6,3F 230,63 EB13 STA EB17 32,17,EB 50,23,235 EBI6 RET C9 201 The Geneva will read voltages internal and external if the ADCVRT BIOS function is called. To call it, you must know the address where this routine resides. The address is equal to the warm boot address plus the hex number 6F. So the operation of reading voltages takes place in these steps. In all CP/M machines, the warm boot address can always be located by reading the data stored in locations 1 and 2. A 16-bit read instruction will read both locations at once, and store the warm boot location in the HL register if this instruction is: LHLD 0001 The LHLD instruction causes the 16-bit read that reads both addresses. The 0001 gives the starting address 1. The WBOOT address has now been read from locations 1 and 2. Next, addition cannot be done in the HL register, so the data stored there must be moved from the HL (called L below) to the accumulator (called A below). All addition is done in the accumulator. This move operation is done with the instruction: MOV A,L Now that the warm boot address has been moved to the accumulator, the next step is to add hex number 6F to it with this instruction: ADI 6F The first stage is now done. The number in the accumulator represents the address of the BIOS routine that calls the ADCVRT routine. We must store this number for future use. This magic number is stored back to the HL register with the command: MOV L,A Finally, the number must be stored one more time in a permanent memory location so it can be called when needed. It is done in this program with the command: SHLD EB0D Now that the address to call has been determined and stored at the address directly after the CALL instruction, the next step is to determine whether the voltage read will be an internal voltage (battery voltage) or an external voltage. To make this choice, a certain number must be placed in the C register just prior to calling the voltage reading routine. In the example below, the internal battery voltage is read by placing a 03 into the C register with the instruction: MVI C, At this point all the preliminary work has been done and it is time to call the routine that will read the voltage. Calling this routine will leave a number in the accumulator that represents the voltage read. This number will be from 0 to 63. If you place a 03 in the register before calling the routine, you read battery voltage. Each of the 64 numbers in the accumulator represents the battery voltages from 0 to 5.7 in 64 steps. If you had placed a 0 in the C register before calling the routine, each of the 64 numbers you find in the accumulator represent external voltages from 0-2 V.D.C. in 64 steps. At this stage all preliminary work has been done. All you need to do now is call the routine. This is done with the command: CALL EA72 NOTE: The EA72 is a dummy address; it is replaced by the data provided by the SHLD instruction above. The Voltage has been read by calling the ADCVRT routine, but the number stored is an 8-bit value and the port is only capable of reading with 6 bits of accuracy. So only 6 of the 8 bits read have useful information. To eliminate the right two useless bits, you issue rotate right commands like this: RAR RAR Masking is performed to get rid of any useless carry bits that may have crept in from the left. This is done with the command: ANI 3F The final results are now stored at a safe location waiting for BASIC to read and convert it. This is done with the command: STA EB17 You have now found the location, called the routine, read the data, removed the useless portions and stored the results. The final step is to return to BASIC. This is done with the command: RET By multiplying a compensation factor by the number you stored, BASIC can now read voltages with an accuracy of 1 part in 64. Notes on Using the ASM Program The machine program shown must be processed by the ASM program to generate the hex file that will be used. Before the ASM program can use the machine file, certain alterations must be performed. First, a starting address is needed for the ORG statement. Since you are using a one-page USER BIOS, the program is ORGed (started) at EB00. Next, the assembler requires that all hex numbers end with the letter H. An example of how this looks when completed is shown below. ORG 0EB00H LHLD 0001 MOV A,L ADI 6FH MOV L,A SHLD 0EB03H MVI C,03H CALL 0EA72H RAR RAR ANI 03FH STA 0EB17H RET Using the HEX file When the ASM program has processed your source machine code file, it produces two new files: one with a .HEX extension, and one with a .PRN extension. It is the .HEX one that you need to get the HEX codes necessary to convert into your BASIC program. The HEX file looks like the one shown below: :10EB00002A01007DC66F6F2203EB0E03CD72EA1F50 :07EB10001FE63F3217EBC9BD :0000000000 There is definite order to this seeming madness. As you can see by looking at the the organized version below, the first portion is starting line numbers and addresses. This is followed by the actual data. The last part of each line is a checksum digit to insure accuracy. The file is terminated by a line of zeros instead of the usual control Z. The only part that is useful is the 16 pairs of numbers on each line (separated by parentheses in the example below). These are the number pairs that are converted to decimal and POKEd into upper memory to form the machine language part of the program. :10 EB00 00 (2A 01 00 7D C6 6F 6F 22 03 EB 0E 03 CD 72 EA 1F) 50 :07 EB10 00 (1F E6 3F 32 17 EB C9) BD :0000000000 The Hex pairs of numbers can be converted in BASIC by using the PRINT command with the &H prefix. For example, to convert the first two pairs on hex numbers above you would enter: PRINT &H2A PRINT &H01 The computer would return these values (in BASIC): 42 1 By repeating this sequence, all of the hex data pairs can be converted to decimal numbers to be poked into the upper memory or the USER BIOS area. There is a simpler way to directly insert the hex data into upper memory. The DDT program will automatically load the file into upper memory if the file has been given a starting address with the ORG command. This is done by entering a statement like this: DDT FILE.HEX