Mastering Sideways ROM & RAM - Module 06 - Osbyte ------------------------------------------------- In Module 5 you were shown how to read an argument which follows a * command. The argument is always read as an ASCII string. This is fine if the string is a word such as "ON" or "OFF" but it can be a problem if the argument is a number. The number must be read as an ASCII string but it has to be converted into binary before the computer can use it as a number. The computer can store a number in the range from &00 to &FF in one byte. In order to read just one byte as an argument up to three ASCII characters representing the byte have to be read, converted into binary and stored in a byte of user memory. You have two options if you want to read numerical arguments. 1. Write a routine to read every ASCII character in the argument and convert the ASCII representation of the number into binary. 2. Use the MOS to do the work for you. The first option, not surprisingly, involves more work than the second. If you write your own conversion routine it has to include error handling and a check for validity. It also has to have somewhere in memory to store the result. This will be covered in detail in Module 9. The second option involves defining a new Osbyte or Osword. Osbyte will be covered in this module and Osword in Module 7. Osbyte is an MOS call specified by the contents of the accumulator taking parameters in the X and Y registers. Osbyte calls 22 (&15) to 116 (&74) are not used by MOS 1.20 and can be defined within your SWR programs. Other operating systems use some of these unused Osbyte calls. For example *FX 114 is used to switch shadow screen memory on and off in the B+ and Master computers. You can use TRACE to check if any particular Osbyte call is unused by your operating system. *FX 100 is not used by any operating system or SWR (as far as I know) and will be used in the example program for this module. Load the object code generated by TRACE into SWR and press the Break key. Type *FX 100 and press Return. You will see that service call 7 is used for an unrecognised Osbyte. Type *FX 100,1,1 and you will see that the Osbyte X and Y parameters are not made available to the interpreter in the X and Y registers as you might expect. Figure 6.1 shows that, whatever value you type for the Osbyte X and Y parameters, the ROM is entered with X storing the ROM number and Y storing zero. >*FX 100 A=07 X=0F Y=00 Unrecognised Osbyte >*FX 100,1,1 A=07 X=0F Y=00 Unrecognised Osbyte >*FX 100,100,100 A=07 X=0F Y=00 Unrecognised Osbyte > Figure 6.1 Service trace for unrecognised Osbyte. ------------------------------------------------- Osbyte uses three consecutive bytes in zero page to store the values of the A, X and Y registers. The accumulator value for the most recent Osbyte (or Osword) is stored in &EF. The X register value is stored in &F0 and the Y register value is stored in &F1. Both Osbyte and Osword make the three registers available to the routine through these three zero page locations. After processing an Osbyte call any values that need to be returned to the calling routine should be stored in locations &F0 and &F1. On return the number stored in the X register and &F0 should be the same and the number stored in the Y register and &F1 should be the same. &EF should store the number of your new Osbyte and the accumulator should be reset to zero before returning to the MOS. On return to the MOS the number stored in &EF is copied into the accumulator. You can use the X and Y parameters of an Osbyte call to make two (one byte) numbers or one (two byte) number available to your new routine. If you want to make a two byte number available store the least significant byte in X and the most significant byte in Y. For example, LDA #100 \ New osbyte LDX #(number MOD 256) \ Least significant byte LDY #(number DIV 256) \ Most significant byte JSR &FFF4 \ Call osbyte Your new Osbyte can then read the least significant byte fron &F0 and the most significant byte from &F1. It can also use these memory locations for post-indexed indirect addressing. Osbytes 0-127 only use the X register, Osbytes 128-255 use both the X and the Y register. When writing a SWR Osbyte routines you should not depend on the contents of Y (held in &F1) and should not return anything in Y (or &F1) when matching Osbytes 0-127. The interpreter needed to implement a new Osbyte is probably the easiest of all interpreters to write. An outline interpreter is shown in figure 6.2. .service PHA \ push accumulator CMP #7 \ is it an unrecognised Osbyte? BEQ newosbyte \ branch if it is .exit PLA \ pull accumulator RTS \ return and give other ROMs the call .newosbyte LDA &EF \ load Osbyte number from &EF CMP #100 \ is it 100? BNE exit \ branch if not Osbyte 100 LDX &F0 \ read X from &F0 LDY &F1 \ read Y from &F1 . . \ new routine goes in here . STX &F0 \ store result for X STY &F1 \ store result for Y PLA \ pull A pushed by interpreter LDA #0 \ other ROMs ignore this call RTS \ return to MOS Figure 6.2 An Osbyte interpreter. --------------------------------- The service entry point of the outline interpreter in figure 6.2 will have a jump to the label ".service". On entering the interpreter the accumulator is pushed on the stack and then compared with 7 to see if the call is an unrecognised Osbyte. If it is not the accumulator is pulled off the stack and control is returned to the MOS. If the call is for an unrecognised Osbyte the number stored in &EF is loaded into the accumulator and compared with the number of the new Osbyte, in this example 100. If the unrecognised Osbyte number stored in &EF is not 100 the accumulator is pulled off the stack and control is returned to the MOS. If it is 100 then the parameters (if used) can be read from &F0 (X register) and &F1 (Y register) and the new routine can be executed. After completing the new routine any results are stored in &F0 (X register) and &F1 (Y register). The index registers and their zero page copies should be identical. Even if you don't want to write any results you should still ensure that the index registers are identical to their zero page copies. The accumulator was pushed on the stack by the interpreter and it is pulled back off to balance the stack. The accumulator is reset to zero to inform other ROMs that the service call has been recognised and control is passed back to the MOS. The interpreter in figure 6.2 is used in the example program OSBYTE. This program generates the object code for *FX 100,x,y in which the X parameter is the number of a ROM to be disabled and the Y parameter is used as a flag which, if set, tells the routine to print a list of the active ROMs. If the X parameter is outside the range from 0 to 15 it is ignored. To use the program load the object code it generates into SWR and press the Break key. You can use the new Osbyte to disable any ROM, except BASIC, until the Break key is pressed to re-enable it. Type *FX 100,100,1 to list the ROMs in your computer. This command will be recognised as the new Osbyte but the X parameter is out of range and it will not disable any ROM. The Y parameter has a value other than zero and the active ROMs will be listed. Type *FX 100,10 to disable ROM &0A without listing the active ROMs. The Y parameter is not specified and takes the default value of zero. Type *FX 100,8,1 to disable ROM &08 and list the active ROMs. You can re-enable all the ROMs disabled with this new Osbyte by pressing the Break key. The program uses an Osbyte interpreter (lines 310-410) similar to the one outlined in figure 6.2. After recognising the new Osbyte (lines 390-410) the new routine loads the X register with the X parameter (line 420) and compares it with &10 (line 430) to see if it is in the range from 0 to 15. If X is in range it disables the ROM by storing a zero in the ROM information table byte that corresponds with that ROM (lines 450-460). The Y parameter is then loaded into the Y register (line 480) and checked to see if it has been set (line 490). If it has been set the active ROMs are listed (line 510). The routine does not write any results to the zero page copies of the index registers but it still ensures that the index registers and their copies are identical (lines 530-540). The accumulator was pushed on the stack by the interpreter (line 320) and it is pulled back off in line 550. It is reset to zero to inform the other ROMs that the service call has been recognised (line 560) and control is returned to the MOS (line 570). The subroutine ROMnames (lines 580-940) uses Osrdrm to list the titles of the active ROMs and is similar to the routine used to print ROM titles in the program READROM (used in Module 1). The subroutine decimal (lines 950-1110) prints the ASCII decimal equivalent of any byte storing a number in the range from 0 to 99 (decimal). It is quite a useful routine and can also be used to print a binary coded decimal byte. 10 REM: OSBYTE 20 MODE7 30 HIMEM=&3C00 40 DIM save 50 50 diff=&8000-HIMEM 60 accumulator=&EF 70 xregister=&F0 80 yregister=&F1 90 ROMpoint=&F6 100 table=&2A1 110 osrdrm=&FFB9 120 osasci=&FFE3 130 osnewl=&FFE7 140 osbyte=&FFF4 150 oscli=&FFF7 160 FOR pass = 0 TO 2 STEP 2 170 P%=HIMEM 180 [ OPT pass 190 BRK 200 BRK 210 BRK 220 JMP service+diff 230 OPT FNequb(&82) 240 OPT FNequb((copyright+diff) MOD 256) 250 BRK 260 OPT FNequs("OSBYTE") 270 .copyright 280 BRK 290 OPT FNequs("(C)1987 Gordon Horsington") 300 BRK 310 .service 320 PHA 330 CMP #7 340 BEQ newosbyte 350 .exit 360 PLA 370 RTS 380 .newosbyte 390 LDA accumulator 400 CMP #100 410 BNE exit 420 LDX xregister 430 CPX #&10 440 BCS checkyreg 450 LDA #0 460 STA table,X 470 .checkyreg 480 LDY yregister 490 BEQ out 500 .ROMlist 510 JSR ROMnames+diff 520 .out 530 LDX xregister 540 LDY yregister 550 PLA 560 LDA #0 570 RTS 580 .ROMnames 590 JSR osnewl 600 LDA #&F 610 PHA 620 .ROMloop 630 TAY 640 LDA table,Y 650 BEQ nothere 660 TYA 670 JSR decimal+diff 680 LDA #ASC(" ") 690 JSR osasci 700 LDA #&8 710 STA ROMpoint 720 LDA #&80 730 STA ROMpoint+1 740 .readloop 750 INC ROMpoint 760 BEQ newline 770 PLA 780 PHA 790 TAY 800 JSR osrdrm 810 CMP #ASC(" ") 820 BCC newline 830 JSR osasci 840 JMP readloop+diff 850 .newline 860 JSR osnewl 870 .nothere 880 PLA 890 SEC 900 SBC #1 910 PHA 920 BPL ROMloop 930 PLA 940 JMP osnewl 950 .decimal 960 SED 970 CLC 980 ADC #0 990 CLD 1000 PHA 1010 LSR A 1020 LSR A 1030 LSR A 1040 LSR A 1050 JSR nibble+diff 1060 PLA 1070 AND #&F 1080 .nibble 1090 CLC 1100 ADC #ASC("0") 1110 JMP osasci 1120 .lastbyte 1130 ] 1140 NEXT 1150 INPUT'"Save filename = "filename$ 1160 IF filename$="" END 1170 $save="SAVE "+filename$+" "+STR$~(HIMEM)+" "+STR$~(las tbyte)+" FFFF8000 FFFF8000" 1180 X%=save 1190 Y%=X% DIV 256 1200 *OPT1,2 1210 CALL oscli 1220 *OPT1,0 1230 END 1240 DEFFNequb(byte) 1250 ?P%=byte 1260 P%=P%+1 1270 =pass 1280 DEFFNequw(word) 1290 ?P%=word 1300 P%?1=word DIV 256 1310 P%=P%+2 1320 =pass 1330 DEFFNequd(double) 1340 !P%=double 1350 P%=P%+4 1360 =pass 1370 DEFFNequs(string$) 1380 $P%=string$ 1390 P%=P%+LEN(string$) 1400 =pass