10 OSBYTE%=&FFF4 20 [ JSR OSBYTE% ...
While on the subject of storing addresses in variables, it is worth pointing out that you can use either real or integer variables to hold addresses but, of course, integer variables will make the assembler work slightly faster and will save storage space in the variables area of memory.
It is a fundamental principle of BASIC that anywhere that you can use a variable or constant you can use an expression. For example, wherever you can write PRINT A, you can also write things like PRINT A*2+4. Unfortunately, not all versions of BASIC keep to this rule in all situations. BBC BASIC sticks to it almost without exception. (You cannot use expressions in place of the constants in *FX commands but these are implemented by the MOS, not by BASIC). Labels are no exception to this general rule and so anywhere you can use a label you can use an expression. For example,
LDA #ASC("A") | Loads the A register with the ASCII code for the letter A. | |
STA DATA+2 | Store the contents of the A register in the memory location whose address is the result of DATA+2 |
LDX (DATA% MOD 256) loads X with the LSB
and
LDY (DATA% DIV 256) loads Y with the MSB
The ability to use expressions to work out addresses in the assembler is very useful. It should be remembered, however, that the expressions are evaluated when the assembly language is being assembled into machine code, and not when the machine code program is run.
The resident integer variable P% has already been discussed in the previous chapter in the context of setting the start address of the memory into which the machine code is loaded. However, P% can be used for much more than just this. The value stored in P% is continually changing during the assembly so as to always hold the address of the memory location that the next byte of machine code will be stored in. Thus, at the end of a successful assembly, P% points to the first memory location after the machine code program. You should now be able to see that the definition of a label using a full stop is the same as setting it equal to the value of P%. In other words:
100 [ 110 LDA DATR 120 .LABEL STR DATA+1 130 JMP LOOP
is the same as
100 [ 110 LDA DATA ] 120 LABEL=P% 130 [ 140 STA DATA+1 150 JMP LOOP ]
Notice that it is necessary to leave the assembler to set the variable 'LABEL' equal to 'P%'. (This idea of leaving assembler in the middle of a program, using some BASIC and returning to the program is useful in itself and will be discussed later.) Not only can the value of P% be assigned to other variables, it can also be altered. For example, if you want to use a few memory locations in the middle of a program to store data then you can leave assembler, increase the value of P% by the number of bytes you require and then return to assembler. If you want to refer to the memory locations by name then all you have to do is set up labels equal to intermediate values of P%. For example, if you want to use three memory locations for data storage and call them DATA1, DATA2 and DATA3 all you have to do is:
assembly language ] DATA1=P% DATA2=P%+1 DATA3=P%+2 P%=P%+3 [ assembly language continued
The line numbers have been left out for clarity. You can use indirection operators to set initial values in memory locations used for data. You could add ?DATA1=33 to the last example if you wanted the memory locations whose address we stored in DATA1 to be initialised to 33.
LDY #0 LDA (ADDRESS),Y
where ADDRESS holds the address of the first of two page zero locations. The effect of this pair of instructions is to load the A register from the location whose address is stored in the two page zero memory locations. In this way pairs of memory locations in page zero can be used as '16-bit pointers' to other memory locations. For this reason the BBC Micro sets aside page zero locations from &70 to &8F for user routines. Page zero locations are in short supply so use somewhere else unless you really need page zero.
IF FAST=1 THEN [ LDA 600:] ELSE [ LDA #300:]
where FAST is a normal BASIC variable that controls which of the two assembly language statements is translated into the machine code program. Notice that the IF is only carried out when the assembly language is being converted to machine code. It is not part of the resulting machine code program. (The colon before the ] can be used instead of a carriage return to end an assembly language statement. In fact the colon can be used to put multiple statements on one line just as in BASIC!)
This selective assembly is a powerful tool and makes the BBC Micro's simple assembler look more like a complex macro assembler. As another example, suppose you needed to carry out the same operation ROR A a number of times. Instead of writing it out each time, why not use:
FOR I=1 TO 4 [ ROR A ] NEXT I
which will include four ROR A instructions in any assembly language program that it is part of. The best way to understand what each of these examples does is to type them in with some other assembly language and see what appears on the listing.
Finally, it is worth pointing out the BBC assembler coupled with BBC BASIC provides a full macro facility! A macro is like a subroutine except that it produces the necessary assembly language each time it is called. Once again, it is easier to understand this idea by means of an example. It is often necessary to add the contents of two memory locations together and store the answer in a third. The following 'macro' generates assembly language to do just this:
DEF PROCADD(N1,N2,ANS) [ CLC LDA N1 ADC N2 STA ANS ] ENDPROC
To add the two numbers in DATA1 and DATA2 and store the answer in DATA3 you would simply write:
PROCADD(DATA1,DATA2,DATA3)
This would generate the correct assembly language at the position at which it was used. You can use PROCADD as many times as you like in a program. A new copy of the assembly language will be produced each time. You can even use local labels within a macro simply by naming them in a LOCAL statement! The possibilities are endless!
1. Sending characters to the printer only.
2. 'Scanning' the text cursor across the screen.
3. Sending carriage returns at the end of each line full of the screen.
Each of these problems is also solved with the help of an MOS call. The resulting program is:
10 DIM MACH% 150 20 OSBYTE%=&FFF4 30 OSWRCH%=&FFEE 40 OSASCI%=&FFE3 50 XCORD%=MACH% 60 YCORD%=MACH%+1 70 CODE%=MACH%+2 80 PROCASMB 90 CLS 100 FOR I=1 TO 24 110 PRINT "TEST OUTPUT ",I 120 NEXT I 130 CALL CODE% 140 STOP 150 DEF PROCASMB 160 FOR PASS=0 TO 3 STEP 3 170 P%=CODE% 180 [OPT PASS 190 LDA #5 200 LDX #1 210 JSR OSBYTE% 220 LDX #0 230 STX XCORD% 240 STY YCORD% 250 .SCAN% LDA #31 260 JSR OSWRCH% 270 LDA XCORD% 280 JSR OSWRCH% 290 LDA YCORD% 300 JSR OSWRCH% 310 JSR DUMP 320 LDX XCORD% 330 INX 340 STX XCORD% 350 CPX #32 360 BNE SCAN% 370 LDX #0 380 STX XCORD% 390 LDX #&0D 400 JSR PRN 410 LDX YCORD% 420 INX 430 STX YCORD% 440 CPX #25 450 BNE SCAN% 460 RTS 470 .DUMP LDA #135 480 JSR OSBYTE% 490 JSR PRN 500 RTS 510 .PRN LDA #2 520 JSR OSWRCH% 530 LDA #21 540 JSR OSWRCH% 550 TXA 560 JSR OSWRCH% 570 LDA #6 580 JSR OSWRCH% 590 LDA #3 600 JSR OSWRCH% 610 RTS 620 ] 630 NEXT PASS 640 ENDPROC
The first few lines (10 to 70) set up the area of memory that the machine code will be stored in and the values of some labels that will be used later. Notice the way that two memory locations are reserved for data storage by lines 50,60 and 70 - the start of the machine code is stored is stored in CODE%. The main program calls PROCASMB to assemble the dump subroutine to machine code and then prints some test lines on the screen before calling it in line 130. The assembly language dump routine can be seen in line 190 to 620. The rest of PROCASMB implements two passes over the assembly language as described in Chapter Seven. The first part of the machine code 190 to 210 sets up a parallel printer as described in the User Guide using the OSBYTE equivalent of *FX5,1. Lines 220 and 240 initialise the data locations XCORD% and YCORD% to zero to start the scan of every location on the screen. XCORD% is used to hold the x co-ordinate and YCORD% is used to hold the y co-ordinate of the text cursor. Lines 250 to 300 use the VDU drivers via OSWRCH to set the cursor to the position stored in XCORD% and YCORD%. Line 300 calls a machine code subroutine DUMP which sends the character under the cursor to the printer (details given later). The rest of the program, from 320 to 460, is concerned with moving the cursor over the screen by updating XCORD% and YCORD%. After each character is dumped to the printer, XCORD% has one added to it (lines 320 to 340) and it is then compared with 32 to see if we have reached the end of a screen line. If we have, then XCORD% is set to zero (lines 370 and 380), a carriage return is sent to the printer to start a new line (lines 390 and 400) and YCORD% has one added to it to take the cursor down one line (lines 410 to 430). At this point YCORD% is compared to 25 (line 440). If it is equal to 25 then all the lines have been dumped and the subroutine ends (line 500). The only things left to explain are the two subroutines DUMP and PRN. DUMP uses the OSBYTE call discussed earlier to get the ASCII code of the character under the cursor into the X register (lines 470 to 480). It then calls subroutine PRN which sends the character in the X register to the printer only. Subroutine PRN starts by turning the printer on with the equivalent of a VDU 2 command (lines 510 and 520). It then disables the VDU drivers so that the character only goes to the printer using the equivalent of VDU 21 (lines 530 and 550). The character is then sent to the printer using OSWRCH (line 550 and 560). The remainder of the subroutine enables the VDU drivers (lines 570 and 580) and then disables the printer (lines 590 and 600).
There is one last part to this project. The dump subroutine is only really useful if it can be in memory at the same time as any BASIC program - not just the one that produces it. This can be done by first saving the whole dump on tape. Then type NEW and type PRINT ~PAGE and write down the answer. Then alter PAGE by typing PAGE=PAGE+&100. This moves the start of any BASIC program &100 memory locations higher up and leaves space for the machine code. Now load the dump program from tape and alter line 10 to read MACH%= 'the old value of PAGE' (which you wrote down earlier). Also delete the test section of the main program, lines 90-130. RUN the program and then delete it using NEW. You can now load and run any other BASIC program you like and obtain a screen dump program by typing CALL 'the old value of PAGE+2'. You can even program a function key to produce the same command and hence dump the screen with a single key stroke!
This program can be easily adapted to dump text from any mode screen by simply changing the values to which XCORD% and YCORD% are compared to detect the end of line and the end of screen respectively. You can even go on to expand the program to dump graphics by using the OSWORD call that returns the value of a single point on the screen but this is more complicated!!
10 DIM INIT% 20 20 DIM CHAROUT% 20 30 DIM CHAREADY% 20 40 DIM CHARGET% 20 50 SERCON%=&FE10 60 ACIACON%=&FE08 70 ACIASTAT%=&FE08 80 ACIATRAN%=&FE09 90 ACIAREC%=&FE09 100 GOSUB 130 110 CALL INIT% 120 GOSUB 490 130 FOR S=0 TO 3 STEP 3 140 P%=INIT% 150 [OPT S 160 LDA #&13 170 STA ACIACON% 180 LDA #&56 190 STA ACIACON% 200 LDA #&49 210 STA SERCON% 220 RTS 230 ] 240 P%=CHAROUT% 250 [OPT S 260 .OUT1 LDA ACIASTAT% 270 AND #&0A 280 BEQ OUT1 290 STX ACIATRAN% 300 RTS 310 ] 320 P%=CHAREADY% 330 [OPT S 340 LDA ACIACON% 350 AND #&01 360 STA &404 370 RTS 380 ] 390 P%=CHARGET% 400 [OPT S 410 LDA ACIAREC% 420 AND #&7F 430 STA &0404 440 RTS 450 ] 460 NEXT S 470 RETURN 480 REM VDU LOOP 490 X%=INKEY(0) 500 IF X%=-1 THEN GOTO 520 510 CALL CHAROUT% 520 CALL CHAREADY% 530 IF A%=0 THEN GOTO 490 540 CALL CHARGET% 550 PRINT CHR$(A%); 560 GOTO 4900
The machine subroutines that make up this program have already been described briefly and should cause no trouble. INIT% sets the baud rate to 1200 but this can easily be changed using the information given in Chapter One. However, notice the alternative way of writing a set of assembly language subroutines using a separate area for each. The main program starts at 490 and forms and infinite loop that can only be broken by pressing ESCAPE or BREAK. Line 490 reads the keyboard to see if a key is being pressed. If one is, then the ASCII code in X% is sent to CHAROUT%. If no key is pressed, CHAREADY% is called to see if there is a character in the receive register. If there is, it is retrieved by CHARGET% (line 540) and printed by line 550.
This program is really only the basis for a full VDU program. It should give the user the option of selecting any baud rate, parity etc., in an extended initialisation section. However, the program given here does illustrate how assembly language and BASIC and be used together and how the resident integer variables can be used to transfer information.
10 MODE 4 20 DIM MACH% 300 30 VDU 23,224,&FF,&FF,&FF,&FF,&FF,&FF,&FF,&FF 40 PROCASMB 50 PROCPLAY 60 STOP 70 DEF PROCASMB 80 OSWRCH%=&FFEE 90 OSBYTE%=&FFF4 100 XCORD%=MACH% 110 YCORD%=MACH%+1 120 XVEL%=MACH%+2 130 YVEL%=MACH%+3 140 CODE%=MACH%+4 150 ?XVEL%=1 160 ?YVEL%=-1 170 FOR PASS=0 TO 3 STEP 3 180 P%=CODE% 190 [OPT PASS 200 STX XCORD% 210 STY YCORD% 220 .LOOP JSR SHOW% 230 JSR MOV% 240 JSR BOUNCE% 250 JSR DELAY% 260 JMP LOOP 270 .SHOW% LDA #8 280 JSR OSWRCH% 290 LDA #32 300 JSR OSWRCH% 310 LDA #31 320 JSR OSWRCH% 330 LDA XCORD% 340 JSR OSWRCH% 350 LDA YCORD% 360 JSR OSWRCH% 370 LDA #224 380 JSR OSWRCH% 390 RTS 400 .MOV% LDA XCORD% 410 CLC 420 ADC XVEL% 430 STA XCORD% 440 LDA YCORD% 450 CLC 460 ADC YVEL% 470 STA YCORD% 480 RTS 490 .BOUNCE% LDA XCORD% 500 CMP #1 510 BEQ FLIPX% 520 CMP #39 530 BNE YBON% 540 .FLIPX% LDA #0 550 SEC 560 SBC XVEL% 570 STA XVEL% 580 .YBON% LDA YCORD% 590 CMP #1 600 BEQ FLIPY% 610 CMP #31 620 BNE FIN% 630 .FLIPY% LDA #0 640 SEC 650 SBC YVEL% 660 STA YVEL% 670 .FIN% RTS 680 .DELAY% LDA #19 690 JSR OSBYTE% 700 RTS 710 ] 720 NEXT PASS 730 ENDPROC 740 DEF PROCPLAY 750 CLS 760 VDU 23,1,0;0;0;0; 770 X%=RND(15) 780 Y%=RND(5)+10 790 CALL CODE% 800 ENDPROC
The main program calls PROCASMB to produce the machine code used by PROCPLAY to bounce the ball around the screen. There are four assembly language subroutines within PROCASMB - MOVE% moves the ball, BOUNCE% makes sure that the ball doesn't go off the screen, SHOW% updates the ball's position on the screen and DELAY% slows things down so that the ball can be seen! PROCPLAY turns off the cursor (line 760) and sets the starting position for the ball at random (lines 770 and 780) before calling the machine code (line 790). The first thing the machine code does is to use X and Y to initialise XCORD% and YCORD% to the starting position. The main loop within the program calls SHOW%, MOV%, BOUNCE% and DELAY% repeatedly (lines 220 to 260). SHOW% is straightforward and uses the equivalent of VDU 31 to move the cursor to the new position (lines 290 to 360). It then prints the ball character using OSWRCH (lines 370 to 390). The only difficult part of the subroutine occurs at the start, where lines 270 to 300 move the cursor back one place and print a blank (ASCII code 32) to remove the old ball's position. The MOV% subroutine updates the X and Y coordinates in XCORD% and YCORD% by adding the contents of XVEL% and YVEL% respectively. Notice how these data bytes are initialised in lines 150 to 160. BOUNCE% is the most complicated subroutine, but all it does is to check to see if the new co-ordinates are on the edge of the screen or not. If they are it reverses one of the velocities contained in XVEL% or YVEL%. Reversing a velocity is done by subtracting it from zero (lines 540 to 570 and 630 to 660). DELAY% is a very simple subroutine and merely carries out the equivalent of an *FX 19. This halts the program until the start of the NEXT TV frame. This means that the ball can only move every fiftieth of a second. If you want to see how fast the ball can really move just remove line 250!
You should be able to add a bat to the ball bouncing routine without too much trouble and have one of the fastest squash games ever!
10 DIM CODE% 100 20 PROCASMB 30 INPUT PL% 40 PROCPULSE(PL%) 50 GOTO 30 60 STOP 70 DEF PROCASMB 80 AUXC%=&FE6B 90 T1CLL%=&FE64 100 T1CH%=&FE65 110 IRQC%=&FE6E 120 P%=CODE% 130 FOR PASS=0 TO 3 STEP 3 140 [OPT PASS 150 LDA #&40 160 STA IRQC% 170 LDA AUXC% 180 ORA #&C0 190 STA AUXC% 200 STX T1CLL% 210 STY T1CH% 220 RTS 230 ] 240 NEXT PASS 250 ENDPROC 260 DEF PROCPULSE(PERIOD%) 270 X%=PERIOD% MOD 256 280 Y%=PERIOD% DIV 256 290 CALL CODE% 300 ENDPROC
Notice once again the different way of forming the machine code by calling PROCASMB which contains the definitions of all the labels used by the machine code. It is a good idea, however, to leave the DIM statement that reserves memory at the start of the program so that it is easy to find. The machine code subroutine first disables any interrupts the timer 1 might try to produce (lines 150 to 160). It then sets up timer 1 in the free running mode with PB7 enabled by storing the correct control bits in the auxiliary control register (line 170 to 190). Finally, it stores the X register into the low order latch and the Y register into the high order counter/latch which starts the counter off. The procedure PROCPULSE(PERIOD%) simply divides the value in PERIOD% into a low byte and a high byte and stores them in X% and Y% respectively. When you run the program, the value that you give to PL% will determine the width of each pulse in the square wave in microseconds. Once a pulse train is started it may be altered by entering a different value of PL%. You might be surprised, however, to discover that the pulses carry on after the program has been stopped by pressing ESCAPE!