GRAPHICS
One of the most exciting and unique features of the ATARI computers is their excellent graphics. When compared with other popular microcomputers for quality of graphics, the ATARI is generally the clear winner. In fact, most arcade-type games available for several different computers look best on the ATARI, and advertising generally utilizes photographs taken from the screen generated on an ATARI.
"But," you say, "that's only available from BASIC." There is a common misconception among ATARI owners that the graphics commands are in the BASIC cartridge, and that commands like PLOT and DRAWTO can't be used without the BASIC cartridge in place. In fact, all of the graphics routines are located in the OS, and are therefore available from any language. We'll now see how to use these routines from assembly language.
Any program which requires such commands as GRAPHICS n, PLOT, or the other graphic commands, generally utilizes these many times throughout the program. It is therefore easiest to present these routines as a set of assembly language subroutines, which can be called from any program. These routines can be saved on a disk as a group and ENTERed into any program requiring graphics routines. To utilize the routines in the program, you'll generally have to load the X and Y registers and the accumulator with parameters that you'd like to implement, and then JSR to the appropriate routine. Note that this parameter passing is discussed in the comments to each routine, to make its use clear. Detailed discussion of the subroutines appears in the section following the program listings.
THE ASSEMBLY LANGUAGE GRAPHICS SUBROUTINES
Listing 10.1
0100 ; ******************************
0110 ; CIO equates
0120 ; ******************************
0340 0130 ICHID = $0340
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0343 0160 ICSTA = $0343
0344 0170 ICBAL = $0344
0345 0180 ICBAH = $0345
0346 0190 ICPTL = $0346
0347 0200 ICPTH = $0347
0348 0210 ICBLL = $0348
0349 0220 ICBLH = $0349
034A 0230 ICAX1 = $034A
034B 0240 ICAX2 = $034B
E456 0250 CIOV = $E456
0260 ; ******************************
0270 ; Other equates needed
0280 ; ******************************
02C4 0290 COLOR0 = $02C4
0055 0300 COLCRS = $55
0054 0310 ROWCRS = $54
02FB 0320 ATACHR = $02FB
00CC 0330 STORE1 = $CC
00CD 0340 STOCOL = $CD
0000 0350 *= $600
0360 ; ******************************
0370 ; The SETCOLOR routine
0380 ; ******************************
0390 ; Before calling this routine,
0400 ; the registers should be set
0410 ; just like the BASIC SETCOLOR:
0420 ; SETCOLOR color,hue,luminance
0430 ; stored respectively in
0440 ; X reg.,accumulator,Y reg.
0450 SETCOL
0600 0A 0460 ASL A ; Need to multiply
0601 0A 0470 ASL A ; hue by 16, and
0602 0A 0480 ASL A ; add it to lumimance.
0603 0A 0490 ASL A ; Now hue is * 16
0604 85CC 0500 STA STORE1 ; temporarily
0606 98 0510 TYA ; So we can add
0607 18 0520 CLC ; Before adding
0608 65CC 0530 ADC STORE1 ; Now have sum
060A 9DC402 0540 STA COLOR0,X ; Actual SETCOLOR
060D 60 0550 RTS ; All done
0560 ; ******************************
0570 ; The COLOR command
0580 ; ******************************
0590 ; For these routines, we will
0600 ; simply store the current COLOR
0610 ; in STOCOL, so the COLOR
0620 ; command simply requires that
0630 ; the accumulator hold the value
0640 ; "n" in the command COLOR n
0650 COLOR
060E 85CD 0660 STA STOCOL ; That's it!
0610 60 0670 RTS ; All done
0680 ; ******************************
6900 ; The GRAPHICS command
0700 ; ******************************
0710 ; The "n" parameter of
0720 ; a GRAPHICS n command will be
0730 ; passed to this routine in the
0740 ; accumulator
0750 GRAFIC
0611 48 0760 PHA ; Store on stack
0612 A260 0770 LDX #$60 ; IOCB6 for screen
0614 A90C 0780 LDA #$C ; CLOSE command
0616 9D4203 0790 STA ICCOM,X ; in command byte
0619 2056E4 0800 JSR CIOV ; Do the CLOSE
061C A260 0810 LDX #$60 ; The screen again
061E A903 0820 LDA #3 ; OPEN command
0620 9D4203 0830 STA ICCOM,X ; in command byte
0623 A9AD 0840 LDA #NAME&255 ; Name is "S:"
0625 9D4403 0850 STA ICBAL,X ; Low byte
0628 A906 0860 LDA #NAME/256 ; High byte
062A 9D4503 0870 STA ICBAH,X
062D 68 0880 PLA ; Get GRAPHICS n
062E 9D4B03 0890 STA ICAX2,X ; Graphics mode
0631 29F0 0900 AND #$F0 ; Get high 4 bits
0633 4910 0910 EOR #$10 ; Flip high bit
0635 090C 0920 ORA #$C ; Read or write
0637 9D4A03 0930 STA ICAX1,X ; n+16, n+32 etc.
063A 2056E4 0940 JSR CIOV ; Setup GRAPHICS n
063D 60 0950 RTS ; All done
0960 ; ******************************
0970 ; The POSITION command
0980 ; ******************************
0990 ; Identical to the BASIC
1000 ; POSITION X,Y command.
1010 ; Since X may be greater than
1020 ; 255 in GRAPHICS 8, we need to
1030 ; use the accumulator for the
1040 ; high byte of X.
1050 POSITN
063E 8655 1060 STX COLCRS ; Low byte of X
0640 8556 1070 STA COLCRS+1 ; High byte of X
0642 8454 1080 STY ROWCRS ; Y position
0644 60 1090 RTS ; All done
1100 ; ******************************
1110 ; The PLOT command
1120 ; ******************************
1130 ; We'll use the X,Y, and A just
1140 ; like in the POSITION command.
1150 PLOT
0645 203E06 1160 JSR POSITN ; To store info
0648 A260 1170 LDX #$60 ; For the screen
064A A90B 1180 LDA #$B ; Put record
064C 9D4203 1190 STA ICCOM,X ; Command byte
064F A900 1200 LDA #0 ; Special case of
0651 9D4803 1210 STA ICBLL,X ; I/O using the
0654 9D4903 1220 STA ICBLH,X ; accumulator
0657 A5CD 1230 LDA STOCOL ; Get COLOR to use
0659 2056E4 1240 JSR CIOV ; Plot the point
065C 60 1250 RTS ; All done
1260 ; ******************************
1270 ; The DRAWTO command
1280 ; ******************************
1290 ; We'll use the X,Y, and A just
1300 ; like in the POSITION command
1310 DRAWTO
065D 203E06 1320 JSR POSITN ; To store info
0660 A5CD 1330 LDA STOCOL ; Get COLOR
0662 8DFB02 1340 STA ATACHR ; Keep CIO happy
0665 A260 1350 LDX #$60 ; The screen again
0667 A911 1360 LDA #$11 ; For DRAWTO
0669 9D4203 1370 STA ICCOM,X ; Command byte
066C A90C 1380 LDA #$C ; As in XIO
066E 9D4A03 1390 STA ICAX1,X ; Auxiliary 1
0671 A900 1400 LDA #0 ; Clear
0673 9D4B03 1410 STA ICAX2,X ; Auxiliary 2
0676 2056E4 1420 JSR CIOV ; Draw the line
0679 60 1430 RTS ; All done
1440 ; ******************************
1450 ; The FILL command
1460 ; ******************************
1470 ; We'll use the X,Y, and A just
1480 ; like in the POSITION command.
1490 ; This is similar to DRAWTO
1500 FILL
067A 203E06 1510 JSR POSITN ; To store info
067D A5CD 1520 LDA STOCOL ; Get COLOR
067F 8DFB02 1530 STA ATACHR ; Keep CIO happy
0682 A260 1540 LDX #$60 ; The screen again
0684 A912 1550 LDA #$12 ; For FILL
0686 9D4203 1560 STA ICCOM,X ; Command byte
0689 A90C 1570 LDA #$C ; As in XIO
068B 9D4A03 1580 STA ICAX1,X ; Auxiliary 1
068E A900 1590 LDA #0 ; Clear
0690 9D4B03 1600 STA ICAX2,X ; Auxiliary 2
0693 2056E4 1610 JSR CIOV ; FILL the area
0696 60 1620 RTS ; All done
1630 ; ******************************
1640 ; The LOCATE command
1650 ; ******************************
1660 ; We'll use the X,Y, and A just
1670 ; like in the POSITION command
1680 ; and the accumulator will
1690 ; contain the LOCATEd color
1700 LOCATE
0697 203E06 1710 JSR POSITN ; To store info
069A A260 1720 LDX #$60 ; The screen again
069C A907 1730 LDA #7 ; Get record
069E 9D4203 1740 STA ICCOM,X ; Command byte
06A1 A900 1750 LDA #0 ; Special case of
06A3 9D4803 1760 STA ICBLL,X ; data transfer
06A6 9D4903 1770 STA ICBLH,X ; in accumulator
06A9 2056E4 1780 JSR CIOV ; Do the LOCATE
06AC 60 1790 RTS ; All done
1800 ; ******************************
1810 ; The screen's name
1820 ; ******************************
06AD 53 1830 NAME .BYTE "S:",$9B
06AE 3A
06AF 9B
0100 ; ******************************
0110 ; CIO equates
0120 ; ******************************
0340 0130 ICHID = $0340
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0343 0160 ICSTA = $0343
0344 0170 ICBAL = $0344
0345 0180 ICBAH = $0345
0346 0190 ICPTL = $0346
0347 0200 ICPTH = $0347
0348 0210 ICBLL = $0348
0349 0220 ICBLH = $0349
034A 0230 ICAX1 = $034A
034B 0240 ICAX2 = $034B
E456 0250 CIOV = $E456
0260 ; ******************************
0270 ; Other equates needed
0280 ; ******************************
02C4 0290 COLOR0 = $02C4
0055 0300 COLCRS = $55
0054 0310 ROWCRS = $54
02FB 0320 ATACHR = $02FB
00CC 0330 STORE1 = $CC
00CD 0340 STOCOL = $CD
0000 0350 *= $600
0360 ; ******************************
0370 ; The SETCOLOR routine
0380 ; ******************************
0390 ; Before calling this routine,
0400 ; the registers should be set
0410 ; just like the BASIC SETCOLOR:
0420 ; SETCOLOR color,hue,luminance
0430 ; stored respectively in
0440 ; X reg.,accumulator,Y reg.
0450 SETCOL
0600 0A 0460 ASL A ; Need to multiply
0601 0A 0470 ASL A ; hue by 16, and
0602 0A 0480 ASL A ; add it to lumimance.
0603 0A 0490 ASL A ; Now hue is * 16
0604 85CC 0500 STA STORE1 ; temporarily
0606 98 0510 TYA ; So we can add
0607 18 0520 CLC ; Before adding
0608 65CC 0530 ADC STORE1 ; Now have sum
060A 9DC402 0540 STA COLOR0,X ; Actual SETCOLOR
060D 60 0550 RTS ; All done
0560 ; ******************************
0570 ; The COLOR command
0580 ; ******************************
0590 ; For these routines, we will
0600 ; simply store the current COLOR
0610 ; in STOCOL, so the COLOR
0620 ; command simply requires that
0630 ; the accumulator hold the value
0640 ; "n" in the command COLOR n
0650 COLOR
060E 85CD 0660 STA STOCOL ; That's it!
0610 60 0670 RTS ; All done
0680 ; ******************************
6900 ; The GRAPHICS command
0700 ; ******************************
0710 ; The "n" parameter of
0720 ; a GRAPHICS n command will be
0730 ; passed to this routine in the
0740 ; accumulator
0750 GRAFIC
0611 48 0760 PHA ; Store on stack
0612 A260 0770 LDX #$60 ; IOCB6 for screen
0614 A90C 0780 LDA #$C ; CLOSE command
0616 9D4203 0790 STA ICCOM,X ; in command byte
0619 2056E4 0800 JSR CIOV ; Do the CLOSE
061C A260 0810 LDX #$60 ; The screen again
061E A903 0820 LDA #3 ; OPEN command
0620 9D4203 0830 STA ICCOM,X ; in command byte
0623 A9AD 0840 LDA #NAME&255 ; Name is "S:"
0625 9D4403 0850 STA ICBAL,X ; Low byte
0628 A906 0860 LDA #NAME/256 ; High byte
062A 9D4503 0870 STA ICBAH,X
062D 68 0880 PLA ; Get GRAPHICS n
062E 9D4B03 0890 STA ICAX2,X ; Graphics mode
0631 29F0 0900 AND #$F0 ; Get high 4 bits
0633 4910 0910 EOR #$10 ; Flip high bit
0635 090C 0920 ORA #$C ; Read or write
0637 9D4A03 0930 STA ICAX1,X ; n+16, n+32 etc.
063A 2056E4 0940 JSR CIOV ; Setup GRAPHICS n
063D 60 0950 RTS ; All done
0960 ; ******************************
0970 ; The POSITION command
0980 ; ******************************
0990 ; Identical to the BASIC
1000 ; POSITION X,Y command.
1010 ; Since X may be greater than
1020 ; 255 in GRAPHICS 8, we need to
1030 ; use the accumulator for the
1040 ; high byte of X.
1050 POSITN
063E 8655 1060 STX COLCRS ; Low byte of X
0640 8556 1070 STA COLCRS+1 ; High byte of X
0642 8454 1080 STY ROWCRS ; Y position
0644 60 1090 RTS ; All done
1100 ; ******************************
1110 ; The PLOT command
1120 ; ******************************
1130 ; We'll use the X,Y, and A just
1140 ; like in the POSITION command.
1150 PLOT
0645 203E06 1160 JSR POSITN ; To store info
0648 A260 1170 LDX #$60 ; For the screen
064A A90B 1180 LDA #$B ; Put record
064C 9D4203 1190 STA ICCOM,X ; Command byte
064F A900 1200 LDA #0 ; Special case of
0651 9D4803 1210 STA ICBLL,X ; I/O using the
0654 9D4903 1220 STA ICBLH,X ; accumulator
0657 A5CD 1230 LDA STOCOL ; Get COLOR to use
0659 2056E4 1240 JSR CIOV ; Plot the point
065C 60 1250 RTS ; All done
1260 ; ******************************
1270 ; The DRAWTO command
1280 ; ******************************
1290 ; We'll use the X,Y, and A just
1300 ; like in the POSITION command
1310 DRAWTO
065D 203E06 1320 JSR POSITN ; To store info
0660 A5CD 1330 LDA STOCOL ; Get COLOR
0662 8DFB02 1340 STA ATACHR ; Keep CIO happy
0665 A260 1350 LDX #$60 ; The screen again
0667 A911 1360 LDA #$11 ; For DRAWTO
0669 9D4203 1370 STA ICCOM,X ; Command byte
066C A90C 1380 LDA #$C ; As in XIO
066E 9D4A03 1390 STA ICAX1,X ; Auxiliary 1
0671 A900 1400 LDA #0 ; Clear
0673 9D4B03 1410 STA ICAX2,X ; Auxiliary 2
0676 2056E4 1420 JSR CIOV ; Draw the line
0679 60 1430 RTS ; All done
1440 ; ******************************
1450 ; The FILL command
1460 ; ******************************
1470 ; We'll use the X,Y, and A just
1480 ; like in the POSITION command.
1490 ; This is similar to DRAWTO
1500 FILL
067A 203E06 1510 JSR POSITN ; To store info
067D A5CD 1520 LDA STOCOL ; Get COLOR
067F 8DFB02 1530 STA ATACHR ; Keep CIO happy
0682 A260 1540 LDX #$60 ; The screen again
0684 A912 1550 LDA #$12 ; For FILL
0686 9D4203 1560 STA ICCOM,X ; Command byte
0689 A90C 1570 LDA #$C ; As in XIO
068B 9D4A03 1580 STA ICAX1,X ; Auxiliary 1
068E A900 1590 LDA #0 ; Clear
0690 9D4B03 1600 STA ICAX2,X ; Auxiliary 2
0693 2056E4 1610 JSR CIOV ; FILL the area
0696 60 1620 RTS ; All done
1630 ; ******************************
1640 ; The LOCATE command
1650 ; ******************************
1660 ; We'll use the X,Y, and A just
1670 ; like in the POSITION command
1680 ; and the accumulator will
1690 ; contain the LOCATEd color
1700 LOCATE
0697 203E06 1710 JSR POSITN ; To store info
069A A260 1720 LDX #$60 ; The screen again
069C A907 1730 LDA #7 ; Get record
069E 9D4203 1740 STA ICCOM,X ; Command byte
06A1 A900 1750 LDA #0 ; Special case of
06A3 9D4803 1760 STA ICBLL,X ; data transfer
06A6 9D4903 1770 STA ICBLH,X ; in accumulator
06A9 2056E4 1780 JSR CIOV ; Do the LOCATE
06AC 60 1790 RTS ; All done
1800 ; ******************************
1810 ; The screen's name
1820 ; ******************************
06AD 53 1830 NAME .BYTE "S:",$9B
06AE 3A
06AF 9B
DISCUSSION OF THE GRAPHICS SUBROUTINES
The first point to note about these routines is that they simply use the standard CIO equates, which we have seen so often before, plus six new ones. We don't need a whole new set of equates, since we're using the standard ATARI CIO routines for all of the graphics commands. Of the six new equates, two are simply storage locations: STOCOL is used to store the COLOR information used in several of the routines, and STORE1 is used for temporary storage of information. These are arbitrarily located at $CD and $CC respectively, but you may feel free to locate them at any safe memory location you choose. One such place would be $100 and $101, which are the bottom two locations of the stack. Another of the new equates is COLOR0, which is the first of the 5 locations used to store color information in the ATARI computers, found in decimal locations 708 to 712. The second is COLCRS, a 2-byte storage location at $55 and $56, which always stores the current column location of the cursor. Since in GRAPHICS 8 there are 320 possible horizontal locations, and we know that each single byte can store only 256 possible values, we need 2 bytes to store all possible horizontal positions of the cursor. However, in all graphics modes other than GRAPHICS 8, it is obvious that location $56 will always be equal to zero. The third new equate is ROWCRS, location $54, which simply keeps track of the vertical position of the cursor. No graphics mode has more than 192 possible vertical positions of the cursor, so only 1 byte is required to store this information. The final new equate is ATACHR, location $2FB, which is used to store the color of the line being drawn in both the FILL and DRAWTO routines.
These routines have been assembled using an origin of $600 for convenience. If you plan to use these in a larger assembly language program, just renumber these subroutines to some high line numbers, such as 25000 and up, and merge these routines with your program before assembling it. This way, you'll have all of the normal graphics commands available from assembly language, without needing to laboriously enter them into each program you write.
The first routine is the assembly language equivalent of the BASIC command SETCOLOR. We know that this is the standard form of the command in BASIC:
SETCOLOR color
register, hue, luminance
In the assembly language subroutine, we first need to load the 6502 registers with the equivalent information. A typical calling routine to use this subroutine to simulate the BASIC command
SETCOLOR
2,4,10
would be as follows:
25
LDX #2
30 LDA #4
35 LDY #10
40 JSR SETCOL
30 LDA #4
35 LDY #10
40 JSR SETCOL
We use the 6-letter form of the name SETCOL for SETCOLOR so that this routine will be compatible with all of the available assemblers for the ATARI. If the assembler you are using allows label names longer than 6 characters, feel free to use the whole routine name. This same convention will be used for all of the graphics routines – for instance, POSITN for POSITION.
To perform the SETCOLOR command, we need to add the luminance to 16 times the hue and store the result into the appropriate color register. To multiply the hue by 16, we'll simply use the accumulator and perform four ASL A instructions. Since each doubles the value contained in the accumulator, the result is 16 times the initial value. After the multiplication, we'll store the result into our temporary storage location and get the luminance into the accumulator with a TYA instruction, setting up for the addition. We then clear the carry bit, as usual prior to addition, and add the result of our previous multiplication to the luminance. Finally, we use the value in the X register, which is the color register desired, as an index into the five color register locations described above. Since we want to SETCOLOR 2 in this example, we loaded the X register with 2 before the call to the subroutine, and the color information is stored in $2C6.
The next routine, the COLOR command, is by far the easiest of all the routines. To call the COLOR equivalent of the BASIC command
COLOR
3
we simply need the following assembly language code:
25
LDA #3
30 JSR COLOR
30 JSR COLOR
The routine simply stores the color selected into our storage location for color, STOCOL, where it will be available for the other graphics routines which require it.
The GRAPHICS command is implemented similiarly. To mimic the BASIC command
GRAPHICS
23
we simply use the following assembly language code:
25
LDA #23
30 JSR GRAFIC
30 JSR GRAFIC
The first thing we need to do is store the graphics mode required. We could store it in STORE1, but pushing it onto the stack is quicker in this case; we don't need to do addition or multiplication, as we did in SETCOLOR. The next four lines of code simply close the screen as a device. This is for insurance. If the screen is already closed, we haven't hurt anything. However, if it's open and we try to reopen it, we'll get an error, so we close it first for insurance. Note that simply by using IOCB6 (loading the X register with $60), we specify the screen, using the default device number assigned by ATARI.
The remainder of the GRAPHICS command simply opens the screen in the particular graphics mode we desire. We again use IOCB6, storing the OPEN command in the command byte of the IOCB in line 830. The name of the screen is S:, and we load the address of this name into ICBAL and ICBAH. The graphics mode is then retrieved from the stack and stored in the second auxiliary byte. The only important bits of the graphics mode in ICAX2 are the lower 4 bits, which specify the graphics mode itself; in this case, GRAPHICS 7. The upper 4 bits control the clearing of the screen, the presence of the text window, and so on, as described in Chapter 8. In this case, we have added 16 to the graphics mode, to eliminate the text window. To isolate these bits, we AND the graphics mode with $F0, which yields the high nibble of the graphics mode. The OS requires that the high bit of this information be inverted, so next we EOR this nibble with $10, to flip the high bit. Finally, we set the low nibble of this byte to $C, to allow either reading or writing to the screen, and we store the byte in ICAX1 of the IOCB. The call to CIO completes our graphics routine and sets up the screen as we had wanted.
As we have already discussed, the POSITION command for GRAPHICS 8 requires 320 possible X locations, so we need 2 bytes to hold this large a number. Therefore, to simulate the command
POSITION
285,73
we will store the low byte of the X coordinate in the X register, the high byte in the accumulator, and the Y coordinate in the Y register, as follows:
25
LDX #30
30 LDA #1
35 LDY #73
40 JSR POSITN
30 LDA #1
35 LDY #73
40 JSR POSITN
Obviously, in any graphics mode other than GRAPHICS 8, the accumulator is always loaded with a zero prior to calling POSITN, and the X register simply contains the X coordinate. The routine itself simply stores the appropriate information into the required locations. The X coordinate is stored into COLCRS and COLCRS+1, and the Y coordinate is stored into ROWCRS.
The PLOT command of
PLOT
258,13
in BASIC is simulated by the following code in assembly language:
25
LDX #3
30 LDA #1
35 LDY #13
40 JSR PLOT
30 LDA #1
35 LDY #13
40 JSR PLOT
This uses the same convention as the POSITN command above. In fact, the PLOT routine begins with a JSR POSITN, which stores the information passed to the routine into the correct locations for use by the OS following the call to CIO. Since we want to output to the screen, we use IOCB6, and the command byte is $B for put record. In this case, we simply want to output a single byte of information, so we use the special case of accumulator I/O accessed by setting the length of the output buffer to zero. Then we load the accumulator with the color information we want to plot, and the call to CIO plots the point for us.
The routines to DRAWTO and FILL are so similar that they will be discussed together. The calling sequence is identical to the PLOT and POSITION commands, so to mimic the BASIC command
DRAWTO
42,80
we use the sequence
25
LDX #42
30 LDA #0
35 LDY #80
40 JSR DRAWTO
30 LDA #0
35 LDY #80
40 JSR DRAWTO
To use the FILL command, simply change line 40 to JSR FILL.
The routine begins with a call to the POSITN routine to store the required information. The color information is then stored in ATACHR, and we use IOCB6 again, loading ICCOM with $11 for DRAWTO and with $12 for FILL. ICAX1 needs a $C, and we clear ICAX2 before completing the routine by calling CIO. These routines are absolutely analogous to the respective BASIC XIO commands which accomplish the same ends. For instance, to draw a line, we can use this command:
XIO
17,#6,12,0,"S:"
In this command, the 17 is the $11 command byte, the #6 is the IOCB number, the 12 is stored in ICAX1, the zero in ICAX2, and the device name is S:. Again, exactly the same XIO command can be use to FILL an area, by simply changing the 17 to 18 ($12).
The final routine, the LOCATE command, is virtually identical to the PLOT command, except that we use the get record command, rather than the put record command. The same use is made of the special single-byte accumulator I/O mode, by setting both ICBLL and ICBLH to zero. The calling routine to duplicate the BASIC command
LOCATE
10,12,A
is as follows:
25
LDX #10
30 LDA #0
35 LDY #12
40 JSR LOCATE
30 LDA #0
35 LDY #12
40 JSR LOCATE
In this case, the accumulator will contain the color value found at the coordinates 10,12 following the call to the LOCATE routine, so a STA command could save this information, or it could be used immediately, by comparing it to some desired value, or in other ways.
This concludes the discussion of the assembly language counterparts to the BASIC graphics commands. Use them in some simple programs, and you'll see how soon they become familiar and how easy they are. In fact, they're almost as easy to use as the BASIC commands. However, since both BASIC and assembly language use the same OS routines to accomplish such operations as DRAWTO and FILL, don't expect that the assembly language routines will be much faster than the BASIC routines you are used to. They will be slightly faster, since you don't have to pay the overhead that BASIC requires in terms of time, but you will experience nowhere near the difference in speed that you have now come to expect when converting from BASIC to assembly language programming. To accomplish this kind of speedup, you'll have to write your own DRAWTO and FILL routines, using a totally different logic from that used by the ATARI OS. Such routines have been written and are much faster than the OS routines, but they are not in the public domain, and you'll have to write your own if speed is critical.
Now that you are becoming proficient in assembly language, you may want to change the ATARI central routines for your own purposes. If you want to try this, purchasing the OS listings and Technical User's Notes from ATARI is highly recommended. You can then look at the commented source code for the OS routines, and modify them for your own routines. Just include them as part of your own programs, making the modifications you would like. However, remember that the code for the OS belongs to ATARI. You can use such modifications in programs for your own use, but be sure to get permission from ATARI before trying to offer for sale any programs containing parts of ATARI's OS. One easy change to try is to allow plotting and drawing without checking for cursor out of range, which slows things down quite a bit. Just be sure that your program calculates the values correctly, or else…
Remember that anything possible from BASIC is also possible from assembly language. One frequently used example of this is animation by means of rotation of the color registers, possible using either the special GTIA modes, or the regular graphics modes. A very simple routine can rotate the standard color registers virtually instantaneously:
15
LDA $708
20 STA STOCOL
25 LDA $709
30 STA $708
35 LDA $710
40 STA $709
45 LDA $711
50 STA $710
55 LDA $712
60 STA $711
65 LDA STOCOL
70 STA $712
20 STA STOCOL
25 LDA $709
30 STA $708
35 LDA $710
40 STA $709
45 LDA $711
50 STA $710
55 LDA $712
60 STA $711
65 LDA STOCOL
70 STA $712
Now that you can draw detailed graphics from assembly language programs, this trick can be used to animate pictures with virtually no slowdown in program execution. For instance, implementation of a down-the-trench type game is simple, by letting rotation of the colors give the illusion of motion down the trench.
PLAYER-MISSILE GRAPHICS FROM ASSEMBLY LANGUAGE
Another exciting feature of the ATARI computers is player-missile graphics. We've already seen an example using an assembly language subroutine to move a player. But in that program, the entire setup for player-missile graphics was in BASIC, and only the routine to move the player was in assembly language. To show how to perform these same operations in a purely assembly language program, this BASIC program has been totally translated into assembly language and is presented below. In this program, decimal addresses are used for the most part, since that is the way the BASIC program was written; this assembly language program is as similiar to that BASIC program as is feasible.
Listing 10.2
0100 ; ******************************
0110 ; CIO equates
0120 ; ******************************
0340 0130 ICHID = $0340
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0343 0160 ICSTA = $0343
0344 0170 ICBAL = $0344
0345 0180 ICBAH = $0345
0346 0190 ICPTL = $0346
0347 0200 ICPTH = $0347
0348 0210 ICBLL = $0348
0349 0220 ICBLH = $0349
034A 0230 ICAX1 = $034A
034B 0240 ICAX2 = $034B
E456 0250 CIOV = $E456
0260 ; ******************************
0270 ; Other equates needed
0280 ; ******************************
00CC 0290 YLOC = $CC ; Indirect address for Y
00CE 0300 XLOC = $CE ; To remember X position
00D0 0310 INITX = $D0 ; Initial X value
00D1 0320 INITY = $D1 ; Initial Y value
0100 0330 STOTOP = $100 ; Temporary storage (ADRESS)
D300 0340 STICK = $D300 ; PORTA - Hardware STICK(0) location
D000 0350 HPOSP0 = $D000 ; Horizontal position Player 0
0000 0360 *= $600
0370 ; ******************************
0380 ; First, lower top of RAM
0390 ; ******************************
0600 A56A 0400 LDA 106 ; Get top of RAM
0602 8D0001 0410 STA STOTOP ; Temporary storage
0605 38 0420 SEC ; Setup for subtract
0606 E908 0430 SBC #8 ; Save 8 pages for PMG
0608 856A 0440 STA 106 ; Tell Atari - new RAMTOP
060A 8D07D4 0450 STA 54279 ; PMBASE
060D 85CF 0460 STA XLOC+1 ; To erase PM RAM
060F A900 0470 LDA #0 ; put indirect
0611 85CE 0480 STA XLOC ; address here.
0490 ; ******************************
0500 ; Next, reset GRAPHICS 0
0510 ; ******************************
0613 A900 0520 LDA #0 ; GRAPHICS 0
0615 48 0530 PHA ; Store on stack
0616 A260 0540 LDX #$60 ; IOCB6 for screen
0618 A90C 0550 LDA #$C ; CLOSE command
061A 9D4203 0560 STA ICCOM,X ; in command byte
061D 2056E4 0570 JSR CIOV ; Do the CLOSE
0620 A260 0580 LDX #$60 ; The screen again
0622 A903 0590 LDA #3 ; OPEN command
0624 9D4203 0600 STA ICCOM,X ; in command byte
0627 A9ED 0610 LDA #NAME&255 ; Name is "S:"
0629 9D4403 0620 STA ICBAL,X ; Low byte
062C A906 0630 LDA #NAME/256 ; High byte
062E 9D4503 0640 STA ICBAH,X
0631 68 0650 PLA ; Get GRAPHICS 0
0632 9D4B03 0660 STA ICAX2,X ; Graphics mode
0635 29F0 0670 AND #$F0 ; Get high 4 bits
0637 4910 0680 EOR #$10 ; Flip high bit
0639 090C 0690 ORA #$C ; Read or write
063B 9D4A03 0700 STA ICAX1,X ; n+16, n+32 etc.
063E 2056E4 0710 JSR CIOV ; Set up GRAPHICS 0
0720 ; ******************************
0730 ; Now set up Player/Missile Graphics
0740 ; ******************************
0641 A978 0750 LDA #120 ; Initial X value
0643 85D0 0760 STA INITX ; Put in place
0645 A932 0770 LDA #50 ; Initial Y value
0647 85D1 0780 STA INITY ; Put in place
0649 A92E 0790 LDA #46 ; Double line
064B 8D2F02 0800 STA 559 ; resolution - SDMCTL
0810 ; ******************************
0820 ; Now clear out PM area of RAM
0830 ; ******************************
064E A000 0840 LDY #0 ; Use as counter
0850 CLEAR
0650 A900 0860 LDA #0 ; Byte to be stored
0652 91CE 0870 STA (XLOC),Y ; Clear 1st byte
0654 88 0880 DEY ; Is page finished?
0655 D0FB 0890 BNE CLEAR ; Page not done yet
0657 E6CF 0900 INC XLOC+1 ; Page is done
0659 A5CF 0910 LDA XLOC+1 ; On to next page
065B CD0001 0920 CMP STOTOP ; Are we done?
065E F0F2 0930 BEQ CLEAR ; One more page
0660 90F0 0940 BCC CLEAR ; Keep going
0950 ; ******************************
0960 ; Now we'll insert the player into
0970 ; the appropriate place in the
0980 ; PMG RAM area
0990 ; ******************************
0662 A56A 1000 LDA 106 ; First, calculate
0664 18 1010 CLC ; correct Y position.
0665 6902 1020 ADC #2 ; PMBASE+512 (2 pages)
0667 85CD 1030 STA YLOC+1 ; High byte of YLOC
0669 A5D1 1040 LDA INITY ; Add Y screen coordinate
066B 85CC 1050 STA YLOC ; For low byte
066D A000 1060 LDY #0 ; As a counter
1070 INSERT
066F B9F006 1080 LDA PLAYER,Y ; Get byte of player
0672 91CC 1090 STA (YLOC),Y ; Put it in place
0674 C8 1100 INY ; For next byte
0675 C008 1110 CPY #8 ; Are we done?
0677 D0F6 1120 BNE INSERT ; No
0679 A5D0 1130 LDA INITX ; Get initial X
067B 8D00D0 1140 STA HPOSP0 ; Tell Atari
067E 85CE 1150 STA XLOC ; Also here
0680 A944 1160 LDA #68 ; Make player red
0682 8DC002 1170 STA 704 ; as in BASIC - PCOLR0
0685 A903 1180 LDA #3 ; To enable Player
0687 8D1DD0 1190 STA 53277 ; Missle Graphics - GRACTL
1200 ; ******************************
1210 ; The main loop - very short
1220 ; ******************************
1230 MAIN
068A 209A06 1240 JSR RDSTK ; Read stick - move player
068D A205 1250 LDX #5 ; To control the
068F A000 1260 LDY #0 ; player, we
1270 DELAY
0691 88 1280 DEY ; have to add
0692 D0FD 1290 BNE DELAY ; a delay - this
0694 CA 1300 DEX ; routine slows
0695 D0FA 1310 BNE DELAY ; things down.
0697 4C8A06 1320 JMP MAIN ; And do it again
1330 ; ******************************
1340 ; Now read the joystick #1
1350 ; ******************************
1360 RDSTK
069A AD00D3 1370 LDA STICK ; Get joystick value
069D 2901 1380 AND #1 ; Is bit 0 = 1?
069F F016 1390 BEQ UP ; No - 11, 12 or 1 o'clock
06A1 AD00D3 1400 LDA STICK ; Get it again
06A4 2902 1410 AND #2 ; Is bit 1 = 1?
06A6 F020 1420 BEQ DOWN ; No - 5, 6 or 7 o'clock
06A8 AD00D3 1430 SIDE LDA STICK ; Get it again
06AB 2904 1440 AND #4 ; Is bit 3 = 1?
06AD F02E 1450 BEQ LEFT ; No - 8, 9 or 10 o'clock
06AF AD00D3 1460 LDA STICK ; Get it again
06B2 2908 1470 AND #8 ; Is bit 4 = 1?
06B4 F02F 1480 BEQ RIGHT ; No - 2, 3 or 4 o'clock
06B6 60 1490 RTS ; Joystick straight up
1500 ; ******************************
1510 ; Now move player appropriately,
1520 ; starting with upward movement.
1530 ; ******************************
06B7 A001 1540 UP LDY #1 ; Setup for moving byte 1
06B9 C6CC 1550 DEC YLOC ; Now 1 less than YLOC
06BB B1CC 1560 UP1 LDA (YLOC),Y ; Get 1st byte
06BD 88 1570 DEY ; To move it up one position
06BE 91CC 1580 STA (YLOC),Y ; Move it
06C0 C8 1590 INY ; Now original value
06C1 C8 1600 INY ; Now set for next byte
06C2 C00A 1610 CPY #10 ; Are we done?
06C4 90F5 1620 BCC UP1 ; No
06C6 B0E0 1630 BCS SIDE ; Forced branch!!!
1640 ; ******************************
1650 ; Now move player down
1660 ; ******************************
06C8 A007 1670 DOWN LDY #7 ; Move top byte first
06CA B1CC 1680 DOWN1 LDA (YLOC),Y ; Get top byte
06CC C8 1690 INY ; to move it down screen
06CD 91CC 1700 STA (YLOC),Y ; Move it
06CF 88 1710 DEY ; Now back to starting value
06D0 88 1720 DEY ; Set for next lower byte
06D1 10F7 1730 BPL DOWN1 ; If Y >= 0 keep going
06D3 C8 1740 INY ; Set to zero
06D4 A900 1750 LDA #0 ; to clear top byte
06D6 91CC 1760 STA (YLOC),Y ; Clear it
06D8 E6CC 1770 INC YLOC ; Now is 1 higher
06DA 18 1780 CLC ; Setup for forced branch
06DB 90CB 1790 BCC SIDE ; Forced branch again
1800 ; ******************************
1810 ; Now side-to-side - left first
1820 ; ******************************
06DD C6CE 1830 LEFT DEC XLOC ; To move it left
06DF A5CE 1840 LDA XLOC ; Get it
06E1 8D00D0 1850 STA HPOSP0 ; Move it
06E4 60 1860 RTS ; Back to MAIN - we're done
1870 ; ******************************
1880 ; Now right movement
1890 ; ******************************
06E5 E6CE 1900 RIGHT INC XLOC ; To move it right
06E7 A5CE 1910 LDA XLOC ; Get it
06E9 8D00D0 1920 STA HPOSP0 ; Move it
06EC 60 1930 RTS ; Back to MAIN - we're done
1940 ; ******************************
1950 ; DATA statements
1960 ; ******************************
06ED 53 1970 NAME .BYTE "S:",$9B
06EE 3A
06EF 9B
06F0 FF 1980 PLAYER .BYTE 255,129,129,129,129,129,129,255
06F1 81
06F2 81
06F3 81
06F4 81
06F5 81
06F6 81
06F7 FF
0100 ; ******************************
0110 ; CIO equates
0120 ; ******************************
0340 0130 ICHID = $0340
0341 0140 ICDNO = $0341
0342 0150 ICCOM = $0342
0343 0160 ICSTA = $0343
0344 0170 ICBAL = $0344
0345 0180 ICBAH = $0345
0346 0190 ICPTL = $0346
0347 0200 ICPTH = $0347
0348 0210 ICBLL = $0348
0349 0220 ICBLH = $0349
034A 0230 ICAX1 = $034A
034B 0240 ICAX2 = $034B
E456 0250 CIOV = $E456
0260 ; ******************************
0270 ; Other equates needed
0280 ; ******************************
00CC 0290 YLOC = $CC ; Indirect address for Y
00CE 0300 XLOC = $CE ; To remember X position
00D0 0310 INITX = $D0 ; Initial X value
00D1 0320 INITY = $D1 ; Initial Y value
0100 0330 STOTOP = $100 ; Temporary storage (ADRESS)
D300 0340 STICK = $D300 ; PORTA - Hardware STICK(0) location
D000 0350 HPOSP0 = $D000 ; Horizontal position Player 0
0000 0360 *= $600
0370 ; ******************************
0380 ; First, lower top of RAM
0390 ; ******************************
0600 A56A 0400 LDA 106 ; Get top of RAM
0602 8D0001 0410 STA STOTOP ; Temporary storage
0605 38 0420 SEC ; Setup for subtract
0606 E908 0430 SBC #8 ; Save 8 pages for PMG
0608 856A 0440 STA 106 ; Tell Atari - new RAMTOP
060A 8D07D4 0450 STA 54279 ; PMBASE
060D 85CF 0460 STA XLOC+1 ; To erase PM RAM
060F A900 0470 LDA #0 ; put indirect
0611 85CE 0480 STA XLOC ; address here.
0490 ; ******************************
0500 ; Next, reset GRAPHICS 0
0510 ; ******************************
0613 A900 0520 LDA #0 ; GRAPHICS 0
0615 48 0530 PHA ; Store on stack
0616 A260 0540 LDX #$60 ; IOCB6 for screen
0618 A90C 0550 LDA #$C ; CLOSE command
061A 9D4203 0560 STA ICCOM,X ; in command byte
061D 2056E4 0570 JSR CIOV ; Do the CLOSE
0620 A260 0580 LDX #$60 ; The screen again
0622 A903 0590 LDA #3 ; OPEN command
0624 9D4203 0600 STA ICCOM,X ; in command byte
0627 A9ED 0610 LDA #NAME&255 ; Name is "S:"
0629 9D4403 0620 STA ICBAL,X ; Low byte
062C A906 0630 LDA #NAME/256 ; High byte
062E 9D4503 0640 STA ICBAH,X
0631 68 0650 PLA ; Get GRAPHICS 0
0632 9D4B03 0660 STA ICAX2,X ; Graphics mode
0635 29F0 0670 AND #$F0 ; Get high 4 bits
0637 4910 0680 EOR #$10 ; Flip high bit
0639 090C 0690 ORA #$C ; Read or write
063B 9D4A03 0700 STA ICAX1,X ; n+16, n+32 etc.
063E 2056E4 0710 JSR CIOV ; Set up GRAPHICS 0
0720 ; ******************************
0730 ; Now set up Player/Missile Graphics
0740 ; ******************************
0641 A978 0750 LDA #120 ; Initial X value
0643 85D0 0760 STA INITX ; Put in place
0645 A932 0770 LDA #50 ; Initial Y value
0647 85D1 0780 STA INITY ; Put in place
0649 A92E 0790 LDA #46 ; Double line
064B 8D2F02 0800 STA 559 ; resolution - SDMCTL
0810 ; ******************************
0820 ; Now clear out PM area of RAM
0830 ; ******************************
064E A000 0840 LDY #0 ; Use as counter
0850 CLEAR
0650 A900 0860 LDA #0 ; Byte to be stored
0652 91CE 0870 STA (XLOC),Y ; Clear 1st byte
0654 88 0880 DEY ; Is page finished?
0655 D0FB 0890 BNE CLEAR ; Page not done yet
0657 E6CF 0900 INC XLOC+1 ; Page is done
0659 A5CF 0910 LDA XLOC+1 ; On to next page
065B CD0001 0920 CMP STOTOP ; Are we done?
065E F0F2 0930 BEQ CLEAR ; One more page
0660 90F0 0940 BCC CLEAR ; Keep going
0950 ; ******************************
0960 ; Now we'll insert the player into
0970 ; the appropriate place in the
0980 ; PMG RAM area
0990 ; ******************************
0662 A56A 1000 LDA 106 ; First, calculate
0664 18 1010 CLC ; correct Y position.
0665 6902 1020 ADC #2 ; PMBASE+512 (2 pages)
0667 85CD 1030 STA YLOC+1 ; High byte of YLOC
0669 A5D1 1040 LDA INITY ; Add Y screen coordinate
066B 85CC 1050 STA YLOC ; For low byte
066D A000 1060 LDY #0 ; As a counter
1070 INSERT
066F B9F006 1080 LDA PLAYER,Y ; Get byte of player
0672 91CC 1090 STA (YLOC),Y ; Put it in place
0674 C8 1100 INY ; For next byte
0675 C008 1110 CPY #8 ; Are we done?
0677 D0F6 1120 BNE INSERT ; No
0679 A5D0 1130 LDA INITX ; Get initial X
067B 8D00D0 1140 STA HPOSP0 ; Tell Atari
067E 85CE 1150 STA XLOC ; Also here
0680 A944 1160 LDA #68 ; Make player red
0682 8DC002 1170 STA 704 ; as in BASIC - PCOLR0
0685 A903 1180 LDA #3 ; To enable Player
0687 8D1DD0 1190 STA 53277 ; Missle Graphics - GRACTL
1200 ; ******************************
1210 ; The main loop - very short
1220 ; ******************************
1230 MAIN
068A 209A06 1240 JSR RDSTK ; Read stick - move player
068D A205 1250 LDX #5 ; To control the
068F A000 1260 LDY #0 ; player, we
1270 DELAY
0691 88 1280 DEY ; have to add
0692 D0FD 1290 BNE DELAY ; a delay - this
0694 CA 1300 DEX ; routine slows
0695 D0FA 1310 BNE DELAY ; things down.
0697 4C8A06 1320 JMP MAIN ; And do it again
1330 ; ******************************
1340 ; Now read the joystick #1
1350 ; ******************************
1360 RDSTK
069A AD00D3 1370 LDA STICK ; Get joystick value
069D 2901 1380 AND #1 ; Is bit 0 = 1?
069F F016 1390 BEQ UP ; No - 11, 12 or 1 o'clock
06A1 AD00D3 1400 LDA STICK ; Get it again
06A4 2902 1410 AND #2 ; Is bit 1 = 1?
06A6 F020 1420 BEQ DOWN ; No - 5, 6 or 7 o'clock
06A8 AD00D3 1430 SIDE LDA STICK ; Get it again
06AB 2904 1440 AND #4 ; Is bit 3 = 1?
06AD F02E 1450 BEQ LEFT ; No - 8, 9 or 10 o'clock
06AF AD00D3 1460 LDA STICK ; Get it again
06B2 2908 1470 AND #8 ; Is bit 4 = 1?
06B4 F02F 1480 BEQ RIGHT ; No - 2, 3 or 4 o'clock
06B6 60 1490 RTS ; Joystick straight up
1500 ; ******************************
1510 ; Now move player appropriately,
1520 ; starting with upward movement.
1530 ; ******************************
06B7 A001 1540 UP LDY #1 ; Setup for moving byte 1
06B9 C6CC 1550 DEC YLOC ; Now 1 less than YLOC
06BB B1CC 1560 UP1 LDA (YLOC),Y ; Get 1st byte
06BD 88 1570 DEY ; To move it up one position
06BE 91CC 1580 STA (YLOC),Y ; Move it
06C0 C8 1590 INY ; Now original value
06C1 C8 1600 INY ; Now set for next byte
06C2 C00A 1610 CPY #10 ; Are we done?
06C4 90F5 1620 BCC UP1 ; No
06C6 B0E0 1630 BCS SIDE ; Forced branch!!!
1640 ; ******************************
1650 ; Now move player down
1660 ; ******************************
06C8 A007 1670 DOWN LDY #7 ; Move top byte first
06CA B1CC 1680 DOWN1 LDA (YLOC),Y ; Get top byte
06CC C8 1690 INY ; to move it down screen
06CD 91CC 1700 STA (YLOC),Y ; Move it
06CF 88 1710 DEY ; Now back to starting value
06D0 88 1720 DEY ; Set for next lower byte
06D1 10F7 1730 BPL DOWN1 ; If Y >= 0 keep going
06D3 C8 1740 INY ; Set to zero
06D4 A900 1750 LDA #0 ; to clear top byte
06D6 91CC 1760 STA (YLOC),Y ; Clear it
06D8 E6CC 1770 INC YLOC ; Now is 1 higher
06DA 18 1780 CLC ; Setup for forced branch
06DB 90CB 1790 BCC SIDE ; Forced branch again
1800 ; ******************************
1810 ; Now side-to-side - left first
1820 ; ******************************
06DD C6CE 1830 LEFT DEC XLOC ; To move it left
06DF A5CE 1840 LDA XLOC ; Get it
06E1 8D00D0 1850 STA HPOSP0 ; Move it
06E4 60 1860 RTS ; Back to MAIN - we're done
1870 ; ******************************
1880 ; Now right movement
1890 ; ******************************
06E5 E6CE 1900 RIGHT INC XLOC ; To move it right
06E7 A5CE 1910 LDA XLOC ; Get it
06E9 8D00D0 1920 STA HPOSP0 ; Move it
06EC 60 1930 RTS ; Back to MAIN - we're done
1940 ; ******************************
1950 ; DATA statements
1960 ; ******************************
06ED 53 1970 NAME .BYTE "S:",$9B
06EE 3A
06EF 9B
06F0 FF 1980 PLAYER .BYTE 255,129,129,129,129,129,129,255
06F1 81
06F2 81
06F3 81
06F4 81
06F5 81
06F6 81
06F7 FF
This program uses many of the routines we have already discussed – an erasing routine to clear out the player-missile area of memory, reading the joystick and moving the player, and the GRAPHICS 0 command. Here, we simply put them all together into one large program which performs all of the tasks necessary to implement a simple example of player-missile graphics in assembly language.
Since it is analogous to the BASIC program we have already written, the assembly version begins by lowering RAMTOP by 8 pages to make room for player-missile memory. Lines 400 to 440 perform this function; line 410 stores the old value of RAMTOP for the erasing routine later. Line 450 tells the ATARI the location of PMBASE, the new value of RAMTOP. We'll use XLOC and XLOC+1 as a temporary indirect address location on page zero, to help in the erase routine. Lines 520 to 710 then reset a GRAPHICS 0 screen below the new location of RAMTOP. Lines 750 to 780 simply store the initial values of X and Y, the screen coordinates where we want the player to appear. These values will be used later, and these lines are here only to keep the analogy with the BASIC program. There is no need to store these values; we could just as easily have used the numbers directly later in the program. However, either way works, and it is slightly easier to change the program later if it is written in this manner. Lines 790 and 800 set up double-line resolution, and then we erase the entire player-missile area in lines 840 to 940.
In the BASIC program, the place in memory where we insert the player to achieve the correct Y positioning on the screen is:
PMBASE+512+INITY
We know that 512 bytes above PMBASE is 2 pages, since each page contains 256 bytes. Therefore, we know that the high byte of this address, in our assembly language program, must be 2 higher than PMBASE. In lines 1000 to 1030, we get PMBASE, add 2, and store the result in YLOC+1, the high byte of the Y position in memory. The low byte is simply INITY, the initial Y position. Remember, the farther down the screen you want the player to appear, the higher in memory the player must be stored.
To insert the player into the correct place in memory, we read one byte at a time from the table of data called PLAYER, and store it using indirect addressing into the memory location we just set up. When Y, our counter, equals 8, we're done, since we started at zero and have only 8 bytes to transfer. If our player had been larger, we simply would have changed the single byte in line 1110 to 1 higher than the number of bytes in the player. The initial X position of the player is read from INITX and stored in the horizontal position register for player zero, 53248. It is also stored in XLOC for use by the move-player routine.
We then make the player red by storing the number 68 into the color register for player zero, 704, and enable player-missile graphics by storing a 3 into 53277, GRACTL.
The main loop of this program is simplicity itself. We JSR to the routine which reads the joystick and moves the player, and then we enter a short delay loop. If we leave out this loop, the player moves so quickly that we can't control it at all! Next, we simply loop back to read the joystick and move the player again.
Obviously, if you want to add some interest to this program, you can insert your own program logic into this main loop, to detect player-playfield collisions, or create obstacles, or anything else you may want in your game. If you are going to lengthen the program, however, you should change the origin to somewhere higher in memory. As it is, this program already occupies virtually all of page 6, so if you make it any larger without changing the origin, it will begin to overwrite DOS and you'll not be able to save or load it. Just change the origin to $6000 or some other safe high memory location. To test the program after assembling it, just type BUG to enter the debugger, and then type G600 for the original version (or G6000 if you change the origin).
Now that you've seen how to implement player-missile graphics from assembly language, you should be able to write your own programs utilizing these same techniques. By doing this, as we've already seen from the need to insert a delay loop in the above program, you'll speed things up enormously and create smooth motion of players to greatly enhance your games. Have fun!
CREATING SOUND ON ATARI COMPUTERS
We will begin our discussion of sound by learning how the ATARI produces sounds, and then we'll write an assembly language subroutine to mimic the BASIC SOUND command.
Let's first look at the equates used for sound generation. The POKEY chip is responsible for the creation of all sounds in the ATARI computers, and it resides in memory from $D200 to $D2FF The sounds which add so much to enjoyment of games, and can even add to the ease of use of business programs if used properly, are divided into four voices. Each voice is controlled by two registers, located in pairs from $D200 to $D207. The first of each pair is the frequency control, and the second of each pair controls both the volume and distortion of the sound produced by that channel. These are:
100 AUDF1 = $D200
; Audio channel 1 frequency
110 AUDC1 = $D201 ; Audio channel 1 control
120 AUDF2 = $D202 ; Audio channel 2 frequency
130 AUDC2 = $D203 ; Audio channel 2 control
140 AUDF3 = $D204 ; Audio channel 3 frequency
150 AUDC3 = $D205 ; Audio channel 3 control
160 AUDF4 = $D206 ; Audio channel 4 frequency
170 AUDC4 = $D207 ; Audio channel 4 control
110 AUDC1 = $D201 ; Audio channel 1 control
120 AUDF2 = $D202 ; Audio channel 2 frequency
130 AUDC2 = $D203 ; Audio channel 2 control
140 AUDF3 = $D204 ; Audio channel 3 frequency
150 AUDC3 = $D205 ; Audio channel 3 control
160 AUDF4 = $D206 ; Audio channel 4 frequency
170 AUDC4 = $D207 ; Audio channel 4 control
The respective frequency registers control the pitch of the sound or note being played. These registers actually divide the sound frequency by the number stored here. That is, if we store a 12 here, then the frequency produced is one-twelfth of the input frequency.
The input frequency is controlled by the initialization of POKEY, by setting AUDCTL. The following chart describes the use of each bit in AUDCTL:
Bit
Use
0 Set to switch main clock from 64 kHz to 15 kHz
1 Set to insert high-pass filter into channel 2
2 Set to insert high-pass filter into channel 1
3 Set to join channels 4 and 3 for 16-bit resolution
4 Set to join channels 2 and 1 for 16-bit resolution
5 Set to clock channel 3 with 1.79 MHz
6 Set to clock channel 1 with 1.79 MHz
7 Set to convert the 17 bit poly-counter into 9 bits
0 Set to switch main clock from 64 kHz to 15 kHz
1 Set to insert high-pass filter into channel 2
2 Set to insert high-pass filter into channel 1
3 Set to join channels 4 and 3 for 16-bit resolution
4 Set to join channels 2 and 1 for 16-bit resolution
5 Set to clock channel 3 with 1.79 MHz
6 Set to clock channel 1 with 1.79 MHz
7 Set to convert the 17 bit poly-counter into 9 bits
What does all this mean? Let's take it one bit at a time. Suppose you store a 10 into the frequency register of voice 1. We already know that this will cause one pulse to come out of that voice for each ten going in. Bit 0 of AUDCTL can switch the frequency of the incoming pulses between 64 kHz and 15 kHz. Kilohertz (kHz) stands for thousands of cycles, or pulses, per second. Obviously, if AUDCTL is set with bit 0 equal to zero, then the output frequency of voice 1 is 6.4 kHz. However, if we store a one into AUDCTL bit 0, then the output frequency of voice 1 will be 1.5 kHz, and a markedly lower tone will result. Bits 5 and 6 work exactly the same way, but if we set these to 1, the voices controlled by them, channels 3 and 1 respectively, produce much higher pitches, since they would be clocked at 1.79 MHz (millions of cycles per second), many times faster than either of the above frequencies.
The two bits 1 and 2 of AUDCTL insert high-pass filters into channels 2 and 1, respectively. These high-pass filters are clocked by channels 4 and 3, respectively. That is, only sounds with a higher frequency than those currently playing in channel 3 will be heard in channel 1, and only sounds with a higher frequency than those currently sounding in channel 4 will be heard in channel 2. Some spectacular special effects are possible using these high-pass filters, and you'll certainly want to experiment to see what can be done.
Since the frequency is stored in a single byte, the ATARI voices are limited to about a five octave range. However, using AUDCTL, it is possible to pair together sets of two voices using bits 3 or 4. This allows the two frequency registers for these voices to form a 16-bit number, giving a nine octave range. This decreases the number of voices available, but it would be perfectly feasible to produce one nine octave voice and two five octave voices, or even two nine octave voices. When two frequency registers are combined into one, the higher of the two frequency registers controls the high byte of the 2-byte number and the lower of the two controls the lower byte.
The high bit of AUDCTL controls the polynomial counter. This is perhaps the most difficult concept of sound generation on the ATARI computers to grasp. Basically, the poly-counters produce a random sequence of pulses which repeats after some time. The higher the number of bits in the poly-counter, the longer the random sequence will be before the pattern repeats. There are three different poly-counters in the ATARI, and they all function as follows.
Suppose you want some noise to be produced. Music is regular in tone, but noise is irregular and is harder to produce. The ATARI generates noise by producing a random sequence of pulses from the poly-counter and effectively ANDing these pulses together with the output of the frequency registers discussed above. Only when both pulses are ON is a sound produced. For example, if the frequency register says a pulse, or sound, should be produced, but the random pattern from the poly-counter is off at that time, no sound is produced. Therefore, although the output of the frequency register's divide-by-n system is a pure frequency, ANDing these pulses with the random sequence generated by the poly-counters produces noise. It should be apparent that different poly-counters produce different noise sounds, and the poly-counter used can be selected by bit 7 of AUDCTL, which is a 9-bit or 17-bit poly-counter.
Furthermore, the distortion, or noise type, of the sound produced can also be changed by the distortion setting, much as in BASIC. The distortion is controlled by the upper 3 bits of the control register for each voice, AUDC1-4. These bits essentially choose how the sound is to be treated and which of the three polycounters is to be used for the distortion, as follows:
Bits
7 6 5 Meaning
0 0 0 Select using 5-bit, then 17-bit poly, divide by 2
0 E 1 Select using 5-bit poly, divide by 2
0 1 0 Select using 5-bit, then 4-bit poly, divide by 2
1 0 0 Select using 17-bit poly, divide by 2
1 E 1 No poly-counters used, just divide by 2
1 1 0 Select using 4-bit poly, divide by 2
7 6 5 Meaning
0 0 0 Select using 5-bit, then 17-bit poly, divide by 2
0 E 1 Select using 5-bit poly, divide by 2
0 1 0 Select using 5-bit, then 4-bit poly, divide by 2
1 0 0 Select using 17-bit poly, divide by 2
1 E 1 No poly-counters used, just divide by 2
1 1 0 Select using 4-bit poly, divide by 2
E in the above table means that bit can be either a 1 or a 0. First, of course, the clock rate is divided by the frequency. Let's look at an example of how the distortion system works. We'll assume that the clock is running at 15 kHz, that we've stored 30 into the appropriate frequency register, and that the 3 high bits of the control register for that voice are 010. First, the clock rate is divided by the frequency register, in this case, 15000/30 = 500 Hz. Next, since the distortion bits are 010, the pulses at 500 Hz output from this operation are effectively ANDed with the output of the 5-bit polynomial counter. The output of this operation is then effectively ANDed with the output of the 4-bit poly-counter, and the frequency of the pulses successfully getting through this entire operation is divided by 2 to produce the final distorted sound.
It should be obvious that with so many options to choose from, the ATARI is capable of many, many, many different sound effects. Some experimentation is clearly in order here. You may hear some really far-out sounds being produced!
A SOUND SUBROUTINE
Creation of sounds on the ATARI computers is extremely easy in BASIC, since the SOUND command allows us to turn any of the four available voices on or off at any desired distortion, pitch, volume, and frequency. Exactly the same functions are available from assembly language. We can write a subroutine to mimic the effects of the BASIC SOUND command, much as we did for the graphics commands in the previous section.
Listing 10.3
0100 ; ******************************
0110 ; SOUND equates
0120 ; ******************************
D200 0130 AUDF1 = $D200 ; Audio 1 frequency
D201 0140 AUDC1 = $D201 ; Audio 1 control
D208 0150 AUDCTL = $D208 ; Audio control
D20F 0160 SKCTL = $D20F ; Serial port control
0101 0170 STORE2 = $101 ; Temporary store
0000 0180 *= $600
0190 ; ******************************
0200 ; The SOUND command
0210 ; ******************************
0220 ; Prior to calling this routine,
0230 ; the X register should contain the
0240 ; voice desired, the accumulator
0250 ; should contain the distortion,
0260 ; the Y register should contain
0270 ; the volume desired, and STORE2
0280 ; should contain the desired
0290 ; frequency.
0300 SOUND
0600 48 0310 PHA ; Store distortion
0601 8A 0320 TXA ; Double voice value
0602 0A 0330 ASL A ; for offset to
0603 AA 0340 TAX ; voice control
0604 AD0101 0350 LDA STORE2 ; Frequency into
0607 9D00D2 0360 STA AUDF1,X ; right channel
060A 8C0101 0370 STY STORE2 ; For use later
060D A900 0380 LDA #0 ; To initialize the
060F 8D08D2 0390 STA AUDCTL ; POKEY chip
0612 A903 0400 LDA #3 ; set these as
0614 8D0FD2 0410 STA SKCTL ; indicated
0617 68 0420 PLA ; Retrieve distortion
0618 0A 0430 ASL A ; Now multiply by
0619 0A 0440 ASL A ; 16 to get the
061A 0A 0450 ASL A ; distortion into
061B 0A 0460 ASL A ; the high nibble
061C 18 0470 CLC ; Setup for add
061D 6D0101 0480 ADC STORE2 ; Add the volume
0620 9D01D2 0490 STA AUDC1,X ; into right voice
0623 60 0500 RTS ; That's all
0100 ; ******************************
0110 ; SOUND equates
0120 ; ******************************
D200 0130 AUDF1 = $D200 ; Audio 1 frequency
D201 0140 AUDC1 = $D201 ; Audio 1 control
D208 0150 AUDCTL = $D208 ; Audio control
D20F 0160 SKCTL = $D20F ; Serial port control
0101 0170 STORE2 = $101 ; Temporary store
0000 0180 *= $600
0190 ; ******************************
0200 ; The SOUND command
0210 ; ******************************
0220 ; Prior to calling this routine,
0230 ; the X register should contain the
0240 ; voice desired, the accumulator
0250 ; should contain the distortion,
0260 ; the Y register should contain
0270 ; the volume desired, and STORE2
0280 ; should contain the desired
0290 ; frequency.
0300 SOUND
0600 48 0310 PHA ; Store distortion
0601 8A 0320 TXA ; Double voice value
0602 0A 0330 ASL A ; for offset to
0603 AA 0340 TAX ; voice control
0604 AD0101 0350 LDA STORE2 ; Frequency into
0607 9D00D2 0360 STA AUDF1,X ; right channel
060A 8C0101 0370 STY STORE2 ; For use later
060D A900 0380 LDA #0 ; To initialize the
060F 8D08D2 0390 STA AUDCTL ; POKEY chip
0612 A903 0400 LDA #3 ; set these as
0614 8D0FD2 0410 STA SKCTL ; indicated
0617 68 0420 PLA ; Retrieve distortion
0618 0A 0430 ASL A ; Now multiply by
0619 0A 0440 ASL A ; 16 to get the
061A 0A 0450 ASL A ; distortion into
061B 0A 0460 ASL A ; the high nibble
061C 18 0470 CLC ; Setup for add
061D 6D0101 0480 ADC STORE2 ; Add the volume
0620 9D01D2 0490 STA AUDC1,X ; into right voice
0623 60 0500 RTS ; That's all
In this program, we double the value originally stored in the X register, which will select the particular voice to be used. This is because the sound registers are arranged in pairs, so each of the control registers and each of the frequency registers are two bytes apart in memory. The frequency is then retrieved from STORE2, which is used because we need four pieces of information for sound, and between the X and Y registers and the accumulator, we have only three storage locations at hand. The frequency is then stored in the appropriate frequency register in line 360, and the volume is temporarily stored until we need it. We then initialize POKEY in lines 380 to 410 and convert the distortion to the upper bits of the accumulator, adding the volume to obtain the number which needs to be stored in the appropriate audio control register to produce the sound desired. That's all there is to it!
However, sound generation in assembly language suffers from the same problem it has in BASIC: no duration can be specified for the sound produced. Either the SOUND command in BASIC, or our equivalent routine in assembly language, simply starts the sound. We must turn the sound off after some predetermined time has elapsed, to create the note or sound effect we want. To turn off the sound, just store a zero into the appropriate control register, AUDC1-4. To measure the time elapsed, use either the timers already discussed, at locations 18 to 20 decimal, or use a routine like the one we used in the player-missile example for short delays:
LDX #50
LDY #0
LOOP DEY
BNE LOOP
DEX
BNE LOOP
LDY #0
LOOP DEY
BNE LOOP
DEX
BNE LOOP
The time delay in this kind of routine is controlled by the initial value loaded into the X register; the larger the number, the longer the delay. These nested loops would take forever to execute if we programmed the counterpart to this routine in BASIC, but after assembly the delay is very short. Try it to see how fast 256 X 50, or 12,800 loops, can be.
COUNTDOWN TIMERS
The third way of keeping track of time on the ATARI computers involves the use of countdown timers. AUDF1, AUDF2, and AUDF4 can act as countdown timers in the following way. When any nonzero value is stored into STIMER ($D209), the values stored in AUDF1, AUDF2, and AUDF4 begin to decrease. Each of these three registers has an appropriate vector location, as outlined below:
Timer
Vector
location
AUDF1 $210, $211 (VTIMR1)
AUDF2 $212, $213 (VTIMR2)
AUDF4 $214, $215 (VTIMR4)
AUDF1 $210, $211 (VTIMR1)
AUDF2 $212, $213 (VTIMR2)
AUDF4 $214, $215 (VTIMR4)
If we place the address of a short routine to turn off sound into one of these vector locations, an interrupt will be generated when the appropriate timer has counted down to zero, and control will shift to your routine to turn off the sound. Just remember to end this routine with an RTI rather than an RTS. Before using this type of timer, you must place the appropriate value into the interrupt request enable byte, IRQEN ($D20E). To enable VTIMR1, set bit 0 of IRQEN; to enable VTIMR2, set bit 1 of IRQEN; and to enable VTIMR4, set bit 2 of IRQEN.
You should now be able to create any sounds available from BASIC using assembly language, and here's one which can't be produced from BASIC because of the speed required. If bit 4 of any of the audio control registers is set to 1, a short "pop" can be heard. This is caused by pushing the speaker cone out once, compressing the air in front of the speaker, which you hear as a "pop:' If we set and reset this bit quickly, we can produce a sound just by compressing air in front of the speaker. Try this:
LOOP
LDA #16
STA AUDC1
(insert delay for frequency)
LDA #31
STA AUDC1
(insert delay for frequency)
JMP LOOP
STA AUDC1
(insert delay for frequency)
LDA #31
STA AUDC1
(insert delay for frequency)
JMP LOOP
The speaker on your TV or monitor will vibrate back and forth, producing sound in a totally different way from that described above. In this example, we are simply moving the speaker directly in order to produce sound.
Return to Table of Contents | Previous Chapter | Next Chapter