Chapter Eight


THE ANTIC CHIP

    In one very important regard, your ATARI computer is unique when compared with most other available microcomputers. Most microcomputers contain a single microprocessor, the 6502 or Z-80 or one of the many others available. Your ATARI, however, has four microprocessors, three of which have been specifically designed by ATARI and are unique to their computers. In this chapter, we will discuss one of these, called ANTIC.

    The ANTIC chip in your ATARI computer is responsible for the video display which is such an important feature of ATARI computers. In most other microcomputers, the microprocessor is responsible not only for calculations and program flow, but also for maintaining the video display. ATARI designed the ANTIC chip to relieve the 6502 of this burden, allowing ANTIC to handle the video display and the 6502 to handle the program which is running.


DISPLAY MEMORY

    A specific area of RAM is set aside in your ATARI to house the information which your program is to display on the TV or monitor screen. We will call this area of RAM the display memory. As with most parts of RAM used for specific purposes in the ATARI computers, display memory has a specific pointer, which can always tell us where display memory is, even if we move it around. Since we may have as much as 48K RAM in a normal ATARI, we need 2 bytes to hold the address of display memory; they are found in locations 88 and 89. In general, whenever you use a GRAPHICS X command, the operating system sets up display memory for graphics mode X just below the top of memory. Since the amount of RAM required for display memory can vary greatly, depending on which graphics mode we have chosen, it is very important to be able to know where in RAM the display memory starts; and these two memory locations can tell us. To determine the beginning of display memory, one line of BASIC is all that's required:

10 BEGDM=PEEK(88)+256*PEEK(89)

This line converts the high and low bytes of the pointer to the beginning of display memory into a single address. Let's see how we can use this information.

    We know that if we issue a GRAPHICS 0 command in BASIC, the screen will clear. What actually happens is that the operating system looks in location 106, which we've used before, to determine where the top of RAM memory is. It then determines how large display memory must be for that particular graphics mode and automatically clears that space in memory, so that when the graphics mode is established, the screen will be clear, and not filled with random garbage. Finally, the display list, which we will discuss shortly, is set up, and control is then passed back to BASIC.

    Once we have a GRAPHICS 0 screen set up, we know that we can get the letter A to appear in the upper left-hand corner of the screen by typing

PRINT "A"

    There is another way to accomplish this same end, however. Now that we know where display memory is located in RAM, we can simply POKE the correct value for the letter A into the appropriate part of display memory, and the letter will appear on the screen, just like it does when we PRINT it to the screen.

POKE BEGDM+2,33

The + 2 in this command allows for the left margin of 2 which is the default left margin on ATARI computers. The 33 stands for the character A in display code. Note that your ATARI actually keeps three separate sets of codes for the meaning of the 256 possible values of the ASCII codes. The first is ATASCII, or ATARI ASCII code, which is used in BASIC; for example:

PRINT CHR$(65)

which will print the letter A to the screen. The second set of codes is the display set, in which the letter A corresponds to a code of 33, as we saw above. This is the code set used when storing information directly into display memory. The third set is called the internal character set; it is used when your ATARI reads the keys of your keyboard, for instance. The most common use of the internal character set is when you would like to know what key was last pressed. Location 764 is a 1-byte buffer which contains the internal code of the last key pressed. If location 764 contains a 255, no key has been pressed. To wait for a key to be pressed, we can write this:

100 POKE 764,255
110 IF PEEK(764)=255 THEN 110

If we want to know which key was pressed, we have to refer to the internal character set. For instance, if PEEK(764) = 127, then the capital letter A was the last key pressed.

    The three character sets used in your ATARI are listed for reference in Appendix 2. We could do all PRINTing to the screen by referring to this list and POKEing the appropriate display codes into the proper place in display memory, as we did with the letter A above.

    Let's try an experiment. We'll POKE the same code, 33, into display memory, but instead of using a GRAPHICS 0 screen, we'll try other graphics modes.

10 FOR MODE=0 TO 8:REM The graphic modes
20 GRAPHICS MODE:REM Set the mode=MODE
30 BEGDM=PEEK(88)+256*PEEK(89):REM Where is display memory?
40 POKE BEGDM+2,33:REM POKE display character A there
50 FOR DELAY=1 TO 700:NEXT DELAY:REM Give a chance to see display
60 NEXT MODE:REM Now for the next mode

When we run this program, we see something very interesting happen. First of all, in GRAPHICS 0, the expected letter A appears in the upper left-hand corner of the screen. In GRAPHICS 1 and 2, moderate- and large-sized yellow letter A's appear in that position, respectively. However, in the other graphics modes, no letter A appears at all, and we just see dots of various colors!


THE DISPLAY LIST

    The reason for these differences between the graphics modes lies in the way display memory is interpreted by ANTIC. If ANTIC just took whatever was in display memory and put it on the screen, it wouldn't be saving the 6502 very much work at all. The 6502 would still have to figure out what the display should look like and then arrange display memory appropriately, all of which would take a great deal of time. Therefore, in the ATARI, the ANTIC chip does this work for the 6502. All the 6502 has to do is set up a short program which the ANTIC chip can understand, telling ANTIC how the 6502 wants the display memory interpreted, and ANTIC does the rest. This program is called the display list. To fully understand the capabilities this display list gives us as programmers, we'll need to learn a new programming language. Fortunately, there aren't many instructions in this language, so it's pretty easy to learn.

    We'll list the instructions here, in both decimal and hexadecimal notation for versatility, and then describe each instruction in detail.

   Hex. Decimal  Instruction                                   
     0      0    Leave 1 blank display line
    10     16    Leave 2 blank display lines
    20     32    Leave 3 blank display lines
    30     48    Leave 4 blank display lines
    40     64    Leave 5 blank display lines
    50     80    Leave 6 blank display lines
    60     96    Leave 7 blank display lines
    70    112    Leave 8 blank display lines
     2      2    Display as GRAPHICS 0 text mode
     3      3    Display as special text mode
     4      4    Display as 4-color text mode
     5      5    Display as large 4-color text mode
     6      6    Display as GRAPHICS 1 text mode
     7      7    Display as GRAPHICS 2 text mode
     8      8    Display as GRAPHICS 3 4-color graphic mode
     9      9    Display as GRAPHICS 4 2-color graphic mode
     A     10    Display as GRAPHICS 5 4-color graphic mode
     B     11    Display as GRAPHICS 6 2-color graphic mode
     C     12    Display as special 160x20, 2-color graphic mode
     D     13    Display as GRAPHICS 7 4-color graphic mode
     E     14    Display as special 160x40, 4-color graphic mode
     F     15    Display as GRAPHICS 8, 1 1/2 color graphic mode
     1      1    Jump to location specified by next two bytes
    41     65    Jump to location specified by next two bytes and
                 wait for vertical blank


Four more instructions can be included by setting 1 of 4 bits in the instruction code to a 1. These are:

   Bit   Instruction                                  
    4    Enable fine vertical scrolling
    5    Enable fine horizontal scrolling
    6    Load memory scan from next two bytes
    7    Set a display list interrupt for the next line

Whew! Seems like a lot, all at once, but if we take it one step at a time, it will be fairly easy. We'll begin by looking at a simple display list. This can be done fairly easily, since, like display memory, the display list has a pointer, found in memory locations 560 and 561, which can always tell us where the display list is located. That makes it easy to write a simple BASIC program to print the display list to the screen so we can have a look at it.

10 GRAPHICS 0:REM Simple display list
20 DL=PEEK(560)+256*PEEK(561):REM Address of display list
30 FOR I=DL TO DL+31:REM Length of display list
40 PRINT PEEK(I);" ";:REM Skip one space between bytes
50 NEXT I:REM Finished printing it

If we run this program, our screen should show something like the following:

112 112 112 66 64 156 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
65 32 156


If you have less than 48K of memory in your computer, the last 2 bytes, and the fifth and sixth bytes may differ from those, as we'll see. Let's dissect this display list one byte at a time, remembering that this display list is a computer program and that the computer in this case is ANTIC. Looking at our list of instructions above, we see that 112 means to leave 8 blank display lines. Since there are 3 112's, that would seem to mean that the beginning of this program is telling ANTIC to leave 24 blank display lines on the screen. Can this be right?

    Most televisions are designed to overscan the visible screen. You may have noticed that on some sets, the output from your computer seems to start closer to the top or closer to the left or right side of the screen than on others. To allow for this difference between TV sets, most display lists begin with these 24 blank display lines. Of course, we need to remember that in GRAPHICS 0, each character is 8 bytes high. Therefore, 24 blank display lines is exactly the amount of space that three lines of GRAPHIC 0 text would occupy. Similarly, the normal screen in GRAPHICS 0 contains 192 display lines (8 times 24). You are free to add another line or two of text to customize a display list, but although it may work fine on your own TV or monitor, it may not work as well on someone else's set.

    The next 3 bytes of the display list were 66, 64, and 156. When we look at the set of possible instructions for ANTIC given above, we don't see 66 listed at all. This 66 is a sum of 64 and 2. The 64 is derived from setting bit 6 of the ANTIC instruction 2. This byte tells ANTIC that we want a line of GRAPHICS 0 displayed here, and, since bit 6 is set, that we also want to load memory scan at this point. Load memory scan means that the next 2 bytes of the display list are a pointer to where ANTIC can find the display memory for that line, and all succeeding lines, until a new load memory scan instruction is encountered. The 2 bytes 64 and 156 are in typical LSB, MSB 6502 order, and to translate them to an address, we add the LSB to 256 times the MSB. Since 64 + 256 * 156 = 40000, we know, as does ANTIC now, that display memory can be found at 40000 and above. The next 23 bytes of the display list are all 2's, and simply tell ANTIC that we want all GRAPHICS 0 lines, consisting of 40 bytes of text per line, each byte 8 display lines high. With the line specified by the load memory scan instruction, that totals 24 lines of GRAPHICS 0, or a normal GRAPHICS 0 screen. The next instruction of the display list is 65, which translates to jump and wait for the vertical blank.


    THE VERTICAL BLANK

    To understand the vertical blank instruction, we must first discuss the method by which the picture on your TV screen is produced. The inner front surface of the picture tube is coated with phosphors, chemicals which emit light when struck by an electron beam. At the rear of the picture tube is an electron gun, which shoots electrons toward the front surface to strike the phosphor-coated surface. The horizontal and vertical position at which this beam of electrons hits the phosphors is controlled by deflecting the beam in a precise way. From the point of view of the person watching the TV, the beam begins in the upper left-hand corner of the screen and traverses a single line across the screen until it reaches the upper right-hand corner. It then jumps back to begin line 2, and so on. The intensity of the beam varies as it scans, producing darker or lighter spots and creating a picture. Devices of this type are called raster-scan devices, and are by far the most common system for producing electronic pictures. The other major type of device is the vector device, in which the beam of electrons draws a line by beginning at the point of origin of the line, and scanning in any direction the line takes until its end is reached. Raster-scan devices draw a line by drawing the whole screen, on which the line happens to be displayed; vector devices draw only the line.

    When a raster-scan device has scanned the entire screen with the electron beam, the position of the beam is returned to the upper left-hand corner, and the device then waits for a synchronization signal, telling it to begin the next screen, or frame. In fact, we can see this pause by adjusting the vertical hold control on a TV set until the picture begins to roll. The wide black horizontal bar which appears to move vertically across the screen is created by the electron beam waiting for the vertical synchronization signal. This interval, during which the electron beam is not scanning across the screen, is called the vertical blank.

    Your ATARI computer produces 262 scan lines for each picture produced on the screen, and the screen is completely redrawn 60 times every second. This seems very fast, but in relation to the speed of the computer, the drawing of the screen is actually proceeding at a snail's pace. The entire drawing of one screen of a display takes 16,684 microseconds, and the vertical blank interval is about 1400 microseconds. If we remember that 1 machine cycle of the computer is less than 1 microsecond, the relative speeds of the computer and the TV become obvious.

    The instruction to jump and wait for the vertical blank, which we encountered above, is actually a 3-byte instruction. It tells ANTIC that its next instruction can be found at the place in memory pointed to by the next 2 bytes, in this case, 32 and 156. This is, in fact, the address of the display list – the same address, found in memory locations 560 and 561, which we discussed above. The instruction to jump and wait for the vertical blank furthermore tells ANTIC not to begin executing the program found at that address until the vertical blank interval is over. This wait accomplishes two things. First, it synchronizes the computer and the TV, so the picture is stable. Second, it gives the computer about 1400 microseconds 60 times per second to use while nothing else is happening. The ATARI uses this time for internal housekeeping, such as updating all of the internal timers and a lot more. We'll discuss some uses for this time later in this chapter.


    PICTURE RESOLUTION

    One final note about the TV picture produced: although 262 scan lines are produced per frame, only 192 of them are visible on most sets, because of overscan, so the highest vertical resolution of the ATARI is 192 pixels (picture elements) in the vertical dimension. In the horizontal dimension, the highest usable resolution is 160 pixels, although GRAPHICS 8 screens actually use 320 pixels of resolution in the horizontal direction. However, in GRAPHICS 8, we are all familiar with the color artifacting which results. If we draw a diagonal line on the screen in GRAPHICS 8, the line appears to be of different colors, depending on its location on the screen. To produce a true color on the screen, two adjacent horizontal pixels should be turned on, or else only one of the primary colors used for broadcast TV may appear when we intended a color such as white to appear. When color rendition is important, our horizontal resolution is limited to 160 pixels.


    DIRECT MEMORY ACCESS

    Now we are beginning to understand how the picture is produced by an ATARI computer. In summary, a portion of memory is used to store the information which is to be displayed (display memory), and this is interpreted by ANTIC using the program called the display list. One further note about this process: ANTIC and the 6502 actually share the area of RAM called the display memory. The 6502 produces and changes the information stored there, and ANTIC reads it, interprets it, and puts it on the screen. It should be apparent that both microprocessors cannot simultaneously access the same memory. In fact, when the 6502 needs it, ANTIC can't access it, and when ANTIC is reading display memory, the 6502 is turned off. ANTIC accesses display memory by a process called Direct Memory Access, or DMA. In doing so, ANTIC actually steals time from the 6502, and during this time no processing is done in the 6502. When ANTIC is finished reading display memory, the 6502 begins processing again. This process of DMA actually slows program execution somewhat; a BASIC program may be speeded up by 30 percent or so by disabling DMA. To disable DMA from BASIC, all that is needed is to

POKE 559,0

To reenable DMA,

POKE 559,34

One serious drawback offsets the increase in speed obtained: your TV screen will turn blank and remain off until DMA is reenabled. However, anything PRINTed to the screen during the time DMA is disabled will appear when DMA is reenabled.

    Now that we know how the TV picture is produced, we can begin to modify it for our own purposes. Many articles have appeared describing how to create custom display lists, such as those combining several different text modes and perhaps even several lines of graphics as well. The remainder of this chapter will be devoted to programs which cannot be written in BASIC, but which can be accessed from BASIC using machine language subroutines; they will perform some rather interesting tasks for us.


INTERRUPT PROCESSING

    Used in the context of this book, an interrupt is a message telling the 6502 to stop whatever was about to happen in your ATARI and instead do something else defined by the programmer. When that task is finished, the 6502 may then continue with whatever it had planned prior to the interrupt. Two types of interrupts are normally used, both of which relate to the TV picture – display list interrupts and vertical blank interrupts. Neither of these can be used without machine language subroutines, since languages such as BASIC are far too slow for these purposes.


DISPLAY LIST INTERRUPTS

    First we'll cover display list interrupts. When we discussed the display list, we noted that if bit 7, the most significant bit, of any display list instruction is set (equal to 1), a display list interrupt is enabled for the next scan line of the TV. What does this mean?

    On page 2 of RAM, in locations $200 and $201 (decimal 512 and 513), is the display list interrupt vector. A vector, as we have discussed before, is like a signpost, pointing somewhere. Normally, location $200 contains $B3 and location $201 contains $E7, so this signpost points to $E7B3. This location contains the byte $40, which is the machine language code for RTI, Return from Interrupt. Another way of saying this is that the display list interrupt vector normally points to an end to an interrupt routine. This is to prevent you from setting a display list interrupt and having the computer go off to some random address and try to execute the code found there.

    Timing considerations are important in the use of display list interrupts. A normal display list interrupt consists of three parts. Part 1 occurs during the time it takes the beam of electrons to finish scanning the line which has bit 7 set. Part 2 occurs between the time that the beam begins scanning the line on which the interrupt takes effect and the time that the beam enters the visible portion of the line. Part 3 begins when the beam enters the visible screen and concludes at the end of the display list interrupt routine.

    The electron beam takes 114 machine cycles to scan each horizontal line. Although bit 7 is set at the beginning of the line, the 6502 is not informed about the interrupt until cycle 36. It is therefore apparent that long machine language routines cannot be implemented using display list interrupts; there is just not enough time for them.


    A SIMPLE EXAMPLE

    Display list interrupts are commonly used to change the background color of the screen in midscreen. Let's write such a routine, and then implement it. Since we will be interrupting the 6502 while it's executing instructions, one thing we must be sure of is that if we plan to use either register or the accumulator, we need to save their initial values and restore those values before returning from the interrupt. Let's look at the program and then discuss it:

Listing 8.1

            0100 ; ******************************
            0110 ; Setup of simple DLI routine
            0120 ; ******************************
0000        0130        *=    $600    ; Safe place for routine
D40A        0140 WSYNC  =     $D40A
D018        0150 COLPF2 =     $D018   ; Background color
            0160 ; ******************************
            0170 ; Now for the DLI routine
            0180 ; ******************************
0600 48     0190        PHA           ; Save value in accumulator
0601 A942   0200        LDA  #$42     ; For a dark red color
0603 8D0AD4 0210        STA  WSYNC    ; See discussion
0606 8D18D0 0220        STA  COLPF2   ; Put new color in
            0230 ; ******************************
            0240 ; Let's restore the accumulator
            0250 ; ******************************
0609 68     0260        PLA           ; Restore it
060A 40     0270        RTI           ; And we're finished

    The first thing we should notice about this routine is that it doesn't begin with a PLA instruction. In fact, the only PLA instruction in the program is to restore from the stack the original value which was in the accumulator; this value was placed on the stack by line 190 for safekeeping during the execution of this routine. Yet this routine is meant to interact with BASIC, and we know that any USR call from BASIC needs the PLA instruction to remove the number of parameters from the stack.

    This apparent error is not going to get us in trouble, since this routine is not meant to be called by a USR call, but rather is accessed directly by the interrupt routine we will set up in our BASIC program shortly. Interrupts need no PLA instruction, since they pass no information to the machine language routine, and therefore the stack remains tidy.

    Let's go through this routine in detail. We first load the accumulator with the hexadecimal number $42, which specifies a dark red color in the ATARI color selection system. This number arises from the sum of 16 times the color, added to the luminance. Since the 4 in $42 is 4 sixteens and the 2 is 2 ones, this represents a color of 4 with a luminance of 2. We store this number in the hardware register for the background color used in GRAPHICS 0, found at address $D018, and called COLPF2 in the ATARI equates system. It is important to understand why we use the hardware register and not the normal color register, which is found at decimal address 710.

    If we store a number (such as $42) representing a color into the normal color register at location 710, the screen will turn red and remain red until we change the number stored in that location. However, this is not what we intended to do with this routine. We wanted only the bottom portion of the screen to turn red while the top portion remains its normal blue color. We need to know that the hardware register, $D018, is updated from its shadow register, 710, 60 times per second. During each vertical blank interval, your ATARI reads the value stored in location 710 and places this value in the hardware register, $D018. Therefore, 60 times per second, the screen is told to turn blue, since the number stored in 710, which is 148, tells the computer a blue color is desired. Now look at what our routine is doing.

    Sixty times per second, between drawing frames of your TV picture, your ATARI is told that the screen background color should be blue. Our routine tells the same hardware register that after a number of lines of the next frame are displayed, the background color should now be red, so it draws the remaining scan lines of that frame with a red background. Then look what happens when that frame is completed and the next vertical blank interval begins. Your ATARI takes 148 and stuffs it into the hardware register, turning the top of the next frame blue again, and our routine turns the bottom of that frame red again, and so on. The net result is that the top of the picture stays blue, and the bottom stays red. If we had used location 710 in line 220 instead of $D018, the whole screen would have remained red.

    Between the loading of the accumulator with the color value desired and the storing of this value into the hardware color register, we see line 210, referring to a WSYNC location at $D40A. This is a very important location for display list interrupts.

    Picture the electron beam scanning over your TV screen from left to right. Every time it gets to the right edge, it jumps back 1 line lower and begins again at the left edge with the next line. If we are doing something such as changing the color displayed for the background, we want to be sure that the color change occurs at the beginning of a line rather than somewhere in the middle. If we simply stick the new color value into the hardware register, the background color will change wherever the electron beam happens to be when the new value is placed into $D018. To prevent this, line 210 stores a value (any value: the color is simply at hand, so we'll use it) to location WSYNC. It doesn't matter what value is stored here; it's the act of storing any value to this location which triggers the resulting action. Whenever a value is POKEd to WSYNC, the computer simply Waits for the horizontal SYNChronization before proceeding. This horizontal synchronization occurs while the electron beam is off the screen, waiting to begin the next line. After synchronization, the computer executes line 220, which stores the desired color into the hardware register. This method ensures that the color change will always take place at the beginning of a scan line and not sometimes in the middle of a line.

    The remainder of this program simply restores the original value which was in the accumulator and then returns to whatever was going on before the interrupt, by means of the RTI (return from interrupt) instruction in line 270.

    Installation of a display list interrupt routine requires some programming in BASIC, since the display list interrupt routine cannot, by itself, cause the desired color change. Let's look at the BASIC program used to implement this particular routine:

10 GOSUB 20000:REM Setup simple DLI routine
20 HIBYTE=INT(ADR(SIMPDLI$)/256):REM Where is our DLI routine?
30 LOBYTE=ADR(SIMPDLI$)-256*HIBYTE:REM Its low byte
40 POKE 512,LOBYTE:REM Set up low byte of new vector
50 POKE 513,HIBYTE:REM Set up high byte
60 DL=PEEK(560)+256*PEEK(561):REM Where is display list?
70 POKE DL+12,PEEK(DL+12)+128:REM Set display list bit 7
80 POKE 54286,192:REM Enable DLIs
90 END :REM But the color change stays
20000 DIM SIMPDLI$(13):REM Relocatable code in string
20010 FOR I=1 TO 13:REM Length of simple DLI routine
20020 READ A:REM Get a byte
20030 SIMPDLI$(I,I)=CHR$(A):REM Put it into string
20040 NEXT I:RETURN :REM Finished
20050 DATA 72,169,66,141,10,212,141,24,208,104
20060 DATA 64,246,243

    As you can see, first we set up the routine we just wrote as a string; this is accomplished in the subroutine at lines 20000 to 20060. We next have to calculate where BASIC has stored this string and break down the address into its high and low bytes. We then can tell the computer where the routine is located, so that when it encounters the display list interrupt instruction, it knows where to turn to find the program it must execute at that time. This information can always be found in the ATARI in memory locations 512 and 513, stored in the usual 6502 fashion of low byte first. Therefore, in lines 40 and 50 we place the 2 bytes of our calculated address into memory locations 512 and 513.

    Line 60 finds the display list for us, and since we've used these instructions before, we'll not further discuss them here. Line 70 sets the display list interrupt bit, bit 7, on the twelfth byte of the display list. We could just as easily have set the color change further down the screen by saying, for instance, DL + 20 instead of DL + 12. Experiment, and look at the results for yourself. Just remember that the display list interrupt enable bit must be set on a valid instruction of the display list. Don't try to set it on one of the 2 bytes of address pointing to display memory (DL + 4 or DL + 5), or one of the 2 bytes of address pointing to the beginning of the display list (the last 2 bytes of the display list).

    Line 80 is critical! Even though we have done everything required to enable display list interrupts, we have not yet told our ATARI that we would like them enabled. We do this in line 80. This instruction is required before display list interrupts will work, and if you have trouble getting display list interrupts to function, check for this line before pulling your machine language code apart looking for a mistake.


    A MORE COMPLICATED EXAMPLE:
    A TABLE-DRIVEN DLI ROUTINE

    There are many uses to which display list interrupts can be put. Some of these are:
  1. Change color of background.
  2. Change color of the characters.
  3. Change the character set entirely (by POKEing the address, in pages, into the appropriate hardware register – $D409, not into 756!).
  4. Invert the character set – may be useful in drawing playing cards to the screen: draw half, then invert the character set and draw the bottom half (hardware register = $D401).
  5. Simulate motion of a horizon via moving DLIs.
Many other uses are possible, limited only by your imagination. We'll give one more example here, simply to show how to implement a more complicated display list interrupt routine. Just remember that time is short, so keep your code as concise and quick as possible.

    This example will introduce table lookup techniques. We will put a display list interrupt on every line of a GRAPHICS 0 display and change the color of the background behind every line produced. To do this, we will construct a table of colors, each of which will be used for a single line of the display. Therefore, we need to read each value in turn from the table and store it to the background hardware color register at the appropriate time. The next time through, we need to get the next value from the table for the next line of the display. We'll construct our table on page 4, but it could also have been placed on page 6 or elsewhere in protected memory, as we have already discussed. The assembly language display list interrupt routine is shown below:

Listing 8.2

            0100 ; ******************************
            0110 ; Set up initial conditions
            0120 ; ******************************
0000        0130        *=   $600
D018        0140 COLPF2 =    $D018
D40A        0150 WSYNC  =    $D40A
0400        0160 OFFSET =    $0400
            0170 ; ******************************
            0180 ; Save registers!!
            0190 ; ******************************
0600 48     0200        PHA           ; Save the accumulator
0601 98     0210        TYA           ; And the Y register
0602 48     0220        PHA           ; Easy way to save it
            0230 ; ******************************
            0240 ; The routine itself
            0250 ; ******************************
0603 AC0004 0260        LDY OFFSET    ; Get initial offset
0606 B90204 0270        LDA OFFSET+2,Y ; Get color from table
0609 8D0AD4 0280        STA WSYNC     ; Wait for horiz. synch.
060C 8D18D0 0290        STA COLPF2    ; Change color
060F EE0004 0300        INC OFFSET    ; For next color
0612 AD0004 0310        LDA OFFSET    ; Are we done?
0615 CD0104 0320        CMP OFFSET+1  ; Stores # of colors
0618 9005   0330        BCC SKIP      ; No - exit DLI routine
061A A900   0340        LDA #0        ; Yes
061C 8D0004 0350        STA OFFSET    ; Reset offset counter
            0360 ; ******************************
            0370 ; Remember to restore registers!
            0380 ; ******************************
061F 68     0390 SKIP   PLA           ; Set up to restore Y
0620 A8     0400        TAY           ; Restore Y
0621 68     0410        PLA           ; Restore accumulator
0622 40     0420        RTI           ; Exit from DLI routine

    Note the differences between this routine and the previous display list interrupt routine. Since this program uses both the accumulator and the Y register, we'll need to save both of these on the stack. This is done by PHAing the accumulator value, then transferring the Y register to the accumulator and PHAing it onto the stack.

    The major difference between the two display list interrupt routines lies in lines 260 to 270 and 300 to 350. We first load the Y register from OFFSET in line 260. The number thus loaded is an offset into the color table, which begins at $402 and continues upward in memory from there. If OFFSET equals 5, then we'll pick the sixth color in the table (remember: the first color is number zero). This becomes the number stored in the hardware background color register at that time. Lines 300 to 350 simply increment OFFSET and determine whether all of the colors have been used. If they have, we reset OFFSET to zero and exit. If not, we simply exit. Note that location $401 (OFFSET + 1) stores the number of colors in the table so that we can determine when we are done.

    Since we saved both the Y register and the accumulator, we'll need to restore them both. We do that in lines 390 to 420 just by reversing the process that saved them.

    Now let's look at the BASIC program that we can use to access our table-driven display list interrupt routine:


10 GOSUB 20000:REM Set up DLI routine in a string
20 HI=INT(ADR(TABLEDLI$)/256):LO=ADR(TABLEDLI$)-HI*256:REM Get
addresses of DLI routine

30 GRAPHICS 0:SETCOLOR 1,0,0:REM Start with black background
40 RESTORE 270:REM Be sure we're reading the right data
50 FOR I=0 TO 27:REM Number of data in table
60 READ A:REM Get a byte
70 POKE 1026+I,A:REM Put the color into the page 4 table
80 NEXT I:REM Finish copying table
90 POKE 1024,0:REM Start with zero offset into table
100 POKE 1025,27:REM Put number of colors here
140 DL=PEEK(560)+256*PEEK(561)+6:REM Normal DL instructions start
with the seventh byte of the display list

150 DLBEG=DL-6:REM The beginning of the display list
160 FOR I=0 TO 2:REM The first 3 bytes are skip 8 scan lines
170 POKE DLBEG+I,240:REM Set DLIs even on the skipped scan lines!!!
180 NEXT I:REM Finish these three
190 POKE DLBEG+I,194:REM Set a DLI even on the "load memory scan"
instruction

200 FOR I=DL TO DL+22:REM Change all of the 2s to 130s
210 POKE I,130:REM Set DLIs
220 NEXT I:REM Finished
230 POKE 512,LO:POKE 513,HI:REM Tell ATARI where our routine is
240 POKE 54286,192:REM Enable the interrupts
250 LIST :REM Gives us something to look at through the colors
260 END :REM All finished
270 DATA 6,22,38,54,70,86,102,118,134,150,166,182,198,214
280 DATA 230,246,246,230,214,198,182,166,150,134,118,102,86,70
290 DATA 54,38,22,6
20000 DIM TABLEDLI$(35):REM Set up string
20010 RESTORE 20060:REM Be sure we're reading correct data
20020 FOR I=1 TO 35:REM Number of bytes in routine
20030 READ A:REM Get a byte
20040 TABLEDLI$(I,I)=CHR$(A):REM Put byte in place in string
20050 NEXT I:RETURN :REM Finish string
20060 DATA 72,152,72,172,0,4,185,2,4,141
20070 DATA 10,212,141,24,208,238,0,4,173,0
20080 DATA 4,205,1,4,144,5,169,0,141,0
20090 DATA 4,104,168,104,64

    The subroutine at line 20000 sets up our routine in a string. Next we find out where the string is stored, and break that address into its high and low bytes. The GRAPHICS 0 command ensures that the display list is set up the way we want it, and we make the background color black initially. We then POKE the color values we would like to see on the screen into place in the table on page 4, one byte at a time. By altering the data in line 270, a different pattern of colors can be obtained. Experiment with these numbers – you'll find it quite easy to produce spectacular effects in your programs. Location $400 (decimal 1024) is POKEd with a zero, since we'd like our routine to begin with the first color in the table. If we were to POKE another number here, say 10, the entire spectrum of colors would be shifted up the screen; we'd start with the eleventh color and end with the tenth.

    Next we find both the beginning of the display list and the beginning of the instructions for GRAPHICS 0 (a 2 as the display list instruction), and we set the high bit on every instruction in the display list, thereby setting a display list interrupt for every line. Note that we can even set display list interrupts for the first three instructions of the display list, which only tell ANTIC to leave 8 blank scan lines. By using this routine, we'll make each group of eight blank scan lines a different color! In line 230, we tell the computer where our display list interrupt routine is, and then in the next line, we enable the display list interrupts. The LIST command in line 250 simply puts some text on the screen and scrolls it through the colors created by the display list interrupt routine, giving quite a nice effect.

    One note about display list interrupt routines: the ATARI computers use WSYNC to create the click accompanying the depression of each key of the keyboard. Therefore, programs which use display list interrupts a great deal, like this one, may be disturbed by pressing keys. The simplest solution to this problem is not to ask for keystrokes in your program if you use display list interrupts frequently. You might, for instance, choose from a menu by use of the joystick, or use the START, SELECT or OPTION keys to make choices. Another note to make is that SYSTEM RESET will, of course, eliminate any display list interrupts which have been set up, since this command sets up a new GRAPHICS 0 display list.


VERTICAL BLANK INTERRUPTS

    A second common type of interrupt used in your ATARI is the vertical blank interrupt, discussed above. Using this system, it's possible to perform multiprocessing on an ATARI computer. In multiprocessing, two programs are being processed simultaneously. Although the use of the vertical blank interrupt cannot produce true multiprocessing, it is possible to set up two programs, so that one is processed in normal time, and one is processed during the vertical blank interval. It will appear that both are being executed simultaneously.

    One excellent example of a program utilizing such multiprocessing is EASTERN FRONT, written by Chris Crawford and available through ATARI. In this game, you take the part of the German Army during Operation Barbarossa, the German invasion of Russia during World War II, and the computer takes the part of the Russian Army. The computer "thinks" about its moves during the vertical blank interval and handles your moves during real time. The longer you think about your move, the more vertical blank intervals pass, and so the more time the computer has to determine its moves.

    A second common use of the vertical blank interval for multiprocessing shows up in a wide variety of programs currently available for the ATARI. Have you noticed the background music which plays while the games are played? This music doesn't slow the game down at all, because it's being played only in the vertical blank interval. Our next program will show how this is done, with a fairly simple example. Although this routine is relocatable, we will simply POKE the routine onto page 6 to access it. By now, you already know how to convert a routine to a string, and you can do so quite easily with this one if you like.

    Two parts are required in any vertical blank interrupt routine. One, of course, is the routine itself. The other is a short routine for installing the vertical blank interrupt routine.

    Normally, as each vertical blank inteval occurs, your ATARI vectors to a specific routine which is executed at every such interval. The routine actually is composed of two parts. The first is called the immediate, and the second is called the deferred vertical blank routine. The vector for the immediate routine is found at $0222. This is a 2-byte address to which the computer jumps in order to execute every immediate vertical blank interrupt routine; it normally points to the service routine beginning at $E45F. This routine terminates by vectoring through locations $0224 and $0225, which contain the address of the deferred vertical blank interrupt routine, normally found at $E462. Diagrammatically, this is as follows:

VBI → $0222 → $E45F → $0224 → $E462 → RTI

    All we have to do to insert our own routine in place of ATARI's normal routine is to direct the vector to our routine instead of ATARI's. To do this, we must first decide which routine we want to replace. As we'll discuss later, long vertical blank interrupt routines have to replace the normal routines; there is not enough time to execute both during one vertical blank interval. Since the immediate routine pointed to by the vector at $0222 is responsible for a lot of the upkeep of the computer, such as updating the system clocks, copying the shadow registers, reading the joysticks and much more, it's safer to keep it going normally, and replace the deferred vertical blank routine; so for this example, we'll use the deferred routine.

    Any time we change a vector, we have a potential problem. With the vertical blank vector, which is used 60 times per second and may be used at any time in relation to the execution of our program, the potential for encountering this problem is magnified. It can best be described by a simple example. We know that the byte stored at $0222 is $5F, and the byte at $0223 is $E4. Let's assume that we'd like to change this vector to point to $0620 instead of $E45F; first we change location $0222 to $20, and then we change $0223 to 6. Simple, wasn't it? But suppose that between the time we change location $0222 to $20 and the time we begin to change location $0223, our computer hits a vertical blank interrupt. It will vector through the address stored in these 2 locations, which is now $E420 because we've changed 1 byte but not the other. Off goes the computer into never-never land, since there is nothing executable at address $E420. To get around this problem, ATARI has provided its own routine to change the vertical blank vectors and prevent this problem from occurring. To see how it works, let's look at the code required:

LDY #$20    ; Low byte of routine
LDX #$06    ; High byte
LDA #07     ; For deferred vector
JSR SETVBV  ; Set the vector
RTS         ; All done

If we wanted to set up our routine for the immediate vertical blank routine, we would load the accumulator with 6 before JSRing to SETVBV ($E45C). That's all there is to it. Remember that your vertical blank interrupt routine must be in place before using this installation routine. If it's not, the computer will crash within a sixtieth of a second after this routine is executed.

    There is, of course, a finite length to each part of the vertical blank interval. The deferred routine is about 20,000 machine cycles long at maximum, and the immediate routine can't be longer than about 2000 machine cycles. If your routine is longer than these limits, the computer will crash, since the TV display and the computer will no longer be able to maintain synchronization.

    Now that we've decided to use the deferred vector and we know how to install our routine, let's look at the routine to play some music in the vertical blank interval, and then we'll discuss it in depth.

Listing 8.3

            0100 ; ******************************
            0110 ; The equates we'll use
            0120 ; ******************************
0000        0130        *=   $0600
00C0        0140 COUNT1 =    $00C0
0224        0150 VVBLKD =    $0224
00C2        0160 COUNT2 =    $00C2
E45C        0170 SETVBV =    $E45C
0660        0180 MUSIC  =    $0660
E462        0190 RETURN =    $E462
D200        0200 SND    =    $D200
D201        0210 VOL    =    $D201
            0220 ; ******************************
            0230 ; PLA to keep the stack clean
            0240 ; ******************************
0600 68     0250        PLA
            0260 ; ******************************
            0270 ; Initialize counters to zero
            0280 ; ******************************
0601 A900   0290        LDA #0
0603 85C0   0300        STA COUNT1    ; Timing counter for notes
0605 85C2   0310        STA COUNT2    ; Which note is playing
            0320 ; ******************************
            0330 ; Now reset deferred vector
            0340 ; ******************************
0607 A020   0350        LDY #$20      ; Low byte of routine
0609 A206   0360        LDX #$06      ; High byte of routine
060B A907   0370        LDA #07       ; We want deferred vector
060D 205CE4 0380        JSR SETVBV    ; Set vector
0610 60     0390        RTS           ; Initialization complete
            0400 ; ******************************
            0410 ; VBI routine itself
            0420 ; ******************************
0611        0430        *=   $0620
0620 E6C0   0440        INC  COUNT1    ; For timing note
0622 A6C0   0450        LDX  COUNT1    ; Is note finished?
0624 E00C   0460        CPX  #12       ; If >=12 it is done
0626 9005   0470        BCC  NO        ; Not yet finished
0628 A900   0480        LDA  #0        ; Yes, so set volume = 0
062A 8D01D2 0490        STA  VOL       ; Now note turned off
062D E00F   0500 NO     CPX  #15       ; 15/60 seconds gone?
062F B003   0510        BCS  PLAY      ; Yes, so play next note
0631 4C62E4 0520        JMP  RETURN    ; No, let it ride
0634 A900   0530 PLAY   LDA  #0        ; Reset counter
0636 85C0   0540        STA  COUNT1    ; For timing
0638 A6C2   0550        LDX  COUNT2    ; Get correct note
063A BD6006 0560        LDA  MUSIC,X   ; From table
063D 8D00D2 0570        STA  SND       ; Set its frequency
0640 A9A6   0580        LDA  #$A6      ; Distortion = 10 ($A)
0642 8D01D2 0590        STA  VOL       ; Volume = 6
0645 E6C2   0600        INC  COUNT2    ; Setup for next note
0647 A6C2   0610        LDX  COUNT2    ; Are we done?
0649 E008   0620        CPX  #8        ; If = 8, we are done
064B 9004   0630        BCC  DONE      ; No
064D A900   0640        LDA  #0        ; Yes - reset counter to
064F 85C2   0650        STA  COUNT2    ; Start over again
0651 4C62E4 0660 DONE   JMP  RETURN    ; All finished
            0670 ; ******************************
            0680 ; Table of Musical Notes
            0690 ; ******************************
0654        0700        *=    $0660
0660 F3     0710        .BYTE 243,243,217,243,204,243,217,243
0661 F3
0662 D9
0663 F3
0664 CC
0665 F3
0666 D9
0667 F3

    The initialization routine sets two counters to zero, one for the number of the note to be played and the other for determining the length of the note. It then installs the vector to our routine, in place of the normal deferred vector. The routine itself begins at $0620 (line 430). We first increment the duration counter. If this equals 12, we turn off the note; otherwise, the note remains playing. The note can be turned off by storing a zero into the hardware register controlling the volume of that voice, in line 490.

    To leave a short pause between notes, we wait until the counter reaches 15 before beginning the next note. To play a new note, we store a zero into the duration counter and get the number of the next note to be played from the note counter. We then use that number as an offset into the table of notes found at $0660 (line 710). Therefore, if COUNT2 equals 2, the third note will be played. The notes are looked up in the table in line 560 and are played by the following three lines. We then increment COUNT2 for the next note and determine if we're done in lines 610 to 630; if we are, we begin the notes all over again by resetting COUNT2 to zero.

    We leave this routine by jumping to RETURN, location $E462, which ends our routine with the normal deferred routine. Had we used the immediate vector for our routine, we would have pointed our exit to $E45F, or, for a really long routine, to $E462, which would have eliminated all of the normal ATARI vertical blank interval processing but gained us a lot of time for our own processing in the vertical blank interval.

    Setting up a vertical blank routine in BASIC is quite simple, as we shall now see for our music-playing routine:

10 GOSUB 19000:REM POKE in initialization routine
20 GOSUB 20000:REM POKE in VBI routine
30 GOSUB 21000:REM POKE in table of notes to be played
40 X=USR(1536):REM Turn on the music!
50 END :REM Will not turn off the music
19000 RESTORE 19050:REM Be sure to get the correct data
19010 FOR I=1536 TO 1552:REM Length of initialization routine
19020 READ A:REM Get a byte
19030 POKE I,A:REM Put it in place
19040 NEXT I:RETURN :REM All done
19050 DATA 104,169,0,133,192,133,194,160,32,162
19060 DATA 6,169,7,32,92,228,96
20000 RESTORE 20050:REM Be sure to read the right data
20010 FOR I=1568 TO 1619:REM Length of VBI routine
20020 READ A:REM Get a byte
20030 POKE I,A:REM Put it in place
20040 NEXT I:RETURN :REM Finished
20050 DATA 230,192,166,192,224,12,144,5,169,0
20060 DATA 141,1,210,224,15,176,3,76,98,228
20070 DATA 169,0,133,192,166,194,189,96,6,141
20080 DATA 0,210,169,166,141,1,210,230,194,166
20090 DATA 194,224,8,144,4,169,0,133,194,76,98,228
21000 RESTORE
21050:REM Read the right data
21010 FOR I=1632 TO 1639:REM Length of the music table
21020 READ A:REM Get a byte
21030 POKE I,A:REM Put it into the table
21040 NEXT I:RETURN :REM All done
21050 DATA 243,243,217,243,204,243,217,243

    This program simply accesses the three subroutines and then USRs to initialize the routine and insert the vector appropriately. The first subroutine POKEs the initialization routine onto page 6, the second POKEs the vertical blank interrupt routine itself onto page 6, and the third POKEs the musical notes table into its proper place on page 6. Line 40 activates the routine through the initialization routine. Voila! You have music to help you through a long programming session. The music will continue to play until you hit SYSTEM RESET, or until you reset the deferred vector to its original value.

    The music played by this routine is actually quite limited. All notes must be of the same length; for instance, all quarter notes or all half notes. Furthermore, only one voice is used. Far more complicated routines are available for the ATARI, to allow you to put intricate multivoiced music into your programs. But now you can even write such a routine yourself.

    One final note concerning the vertical blank interval: one extremely powerful use of this feature is for reading the joysticks and moving players around the screen. By putting this routine into the vertical blank interval, we can remove one of the most time-consuming parts of most BASIC programs, and allow the computer to read the joysticks and update player positions 60 times per second, without slowing down the real-time action at all. You might want to try converting the joystick routine we wrote in Chapter 7 into one utilized in the vertical blank interval, as an exercise for yourself.


FINE SCROLLING

    We have yet to cover the final two bits of the display list instructions: the horizontal and vertical fine scroll enable bits (bits 4 and 5, respectively). The fine scrolling facility enables programmers to produce some of the most interesting and exciting effects on the ATARI – programs which scroll a seemingly endless screen past the player. In fact, one of the nicest examples of fine scrolling is found in EASTERN FRONT, already mentioned for its use of multiprocessing. A detailed map of Eastern Europe can be scrolled over many normal-sized screens; action takes place all over the map, making for an exciting and challenging experience.

    We will now cover an example of fine horizontal scrolling and discuss fine vertical scrolling to enable you to write your own vertical fine scrolling routines. Horizontal fine scrolling has one difficulty we must first deal with. As you know by now, a normal GRAPHICS 0 display list contains the ANTIC code 2 for each line, telling ANTIC that we want the next 40 bytes of display memory to be interpreted as text and placed on the screen accordingly. However, a problem arises when we scroll the information on the screen to the left. Let's look at an example to see the problem graphically:

Screen Column Number
                    111111111122222222223333333333
          0123456789012345678901234567890123456789
.
.
Line 5    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Line 6    bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
Line 7    cccccccccccccccccccccccccccccccccccccccc
.
.

As long as we only have 40 characters to display, there is no problem. However, how can we scroll the display window over this information? For instance, if we try to scroll the screen to the right (scroll the information to the left), what will the last character on each line be? Line 5 will now end with a b, line 6 with a c, and so on. This is not true horizontal scrolling, but actually mixed horizontal and vertical scrolling.

    In order to achieve true horizontal scrolling, we need a special form of the display list. We need to build a custom display list which has room for more than 40 characters per line, so that when we scroll, we get to see information which was previously hidden off-screen. Fortunately, we already know the techniques required to build such a custom display list. We'll need to have a separate Load Memory Scan option on every line, and we'll need to reserve enough memory for each line to be far more than 40 bytes long. Let's design our display list with each line 250 bytes long, so our display memory will be over 6 times wider than a normal GRAPHICS 0 screen. That will give us plenty of room to scroll. The display looks like this:

Screen Column Number
               111111111122222222223333333333
     0123456789012345678901234567890123456789
.
.
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
.
.

    Diagrammatically, we can now see that the display memory for each line of the display is wider than the screen itself. This now gives us room to move the screen from side to side over the data, without getting the artificial vertical scrolling of the b's into the a line, and so on.

    The second feature of our display list which we must consider is that each LMS instruction must also have bit 4 set, so we must add 16 to the LMS instruction 64. Of course, we must also add the ANTIC instruction for the interpretation of the data. In this case, we'll use GRAPHICS 0 (ANTIC mode 2), so we'll need to add 2 to this sum. The total of these is 64 + 16 + 2, or 82, which will be the instruction for every line of our custom display list.

    We could, of course, construct our modified display list from BASIC, much as we saw above, but let's experiment and write an assembly language program to construct this display list for us. We'll locate the new display list on page 6, where it will be safe. Remember, each display list begins with 24 blank scan lines, then continues with 24 lines of ANTIC codes before the JVB instruction that terminates the list. Let's look at a program which can construct such a display list for us:

Listing 8.4

            0100 ; ******************************
            0110 ; Origin and equates
            0120 ; ******************************
0000        0130        *=   $600    ; Must be in string
0600        0140 DLIST  =    $0600   ; Where DL will be
0058        0150 SAVMSC =    $58     ; Display memory address
0230        0160 SDLSTL =    $230    ; DL address
E45C        0170 SETVBV =    $E45C   ; To set VB vector
            0180 ; ******************************
            0190 ; Initialization routine to set
            0200 ; up new display list and
            0210 ; insert the scrolling routine

            0220 ; into the vertical blank
            0230 ; interrupt.
            0240 ; ******************************
0600 68     0250 INIT   PLA          ; Keep stack neat
0601 A970   0260        LDA #$70     ; 8 blank scan lines
0603 8D0006 0270        STA DLIST    ; Into the first
0606 8D0106 0280        STA DLIST+1  ; 3 lines of the
0609 8D0206 0290        STA DLIST+2  ; Display List
060C A018   0300        LDY #24      ; # of lines in DL
060E A203   0310        LDX #3       ; Set counter
0610 A952   0320        LDA #82      ; EMS + Graphics 0 + scroll
0612 9D0006 0330        STA DLIST,X  ; Into display list
0615 E8     0340        INX          ; Keep counter going
0616 A558   0350        LDA SAVMSC   ; Get display memory address
0618 9D0006 0360        STA DLIST,X  ; Into display list
061B E8     0370        INX          ; Keep counter going
061C A559   0380        LDA SAVMSC+1 ; Get high byte
061E 38     0390        SEC          ; Set up for subtract
061F E918   0400        SBC #24      ; Make room for display
0621 9D0006 0410        STA DLIST,X  ; Into display list
0624 E8     0420        INX          ; Keep counter going
0625 88     0430        DEY          ; One line finished
0626 A952   0440 LOOP   LDA #82      ; LMS + horizontal scroll
0628 9D0006 0450        STA DLIST,X  ; Into display list
062B E8     0460        INX          ; Keep counter going
062C BDFD05 0470        LDA DLIST-3,X ; Get last memory
062F 18     0480        CLC          ; Set up for addition
0630 69FA   0490        ADC #250     ; Line is 250 bytes
0632 9D0006 0500        STA DLIST,X  ; Into display list
0635 E8     0510        INX          ; Keep counter going
0636 BDFD05 0520        LDA DLIST-3,X ; Get high byte
0639 6900   0530        ADC #0       ; See discussion
063B 9D0006 0540        STA DLIST,X  ; Into display list
063E E8     0550        INX          ; Keep counter going
063F 88     0560        DEY          ; Another line done
0640 D0E4   0570        BNE LOOP     ; Finished? NO
0642 A941   0580        LDA #65      ; YES - JVB instruction
0644 9D0006 0590        STA DLIST,X  ; Into display list
0647 E8     0600        INX          ; Keep counter going
0648 A900   0610        LDA #0       ; Page 6 low byte
064A 9D0006 0620        STA DLIST,X  ; Into display list
064D 8D3002 0630        STA SDLSTL   ; Tell Atari also
0650 E8     0640        INX          ; Keep counter going
0651 A906   0650        LDA #6       ; Page 6 high byte
0653 9D0006 0660        STA DLIST,X  ; Into display list
0656 8D3102 0670        STA SDLSTL+1 ; Tell Atari
            0680 ; ******************************
            0690 ; Insert scrolling routine into
            0700 ; the deferred vertical blank.
            0710 ; ******************************
0659 68     0720        PLA          ; Get routine's address
065A AA     0730        TAX          ; To X register
065B 68     0740        PLA          ; Finish address
065C A8     0750        TAY          ; To Y register
065D A907   0760        LDA #$07     ; Deferred vector
065F 205CE4 0770        JSR SETVBV   ; Set the vector
0662 60     0780        RTS          ; All finished

    We begin by putting the three lines that each mean to leave 8 blank scan lines, $70, at the top of our new display list. Next, we load the Y register with 24; we'll use this to keep track of how many lines of the display list we've constructed. The X register is set to 3, since we want to skip over the first three $70 instructions. The next instruction we need in the display list is 82, so we store it appropriately in lines 320 and 330. We then increment our X counter, since we have added a byte to the growing display list. We'll need to increment this counter with each byte added.

    Since each line is an LMS instruction, the next 2 bytes of the line must be the address of display memory from which ANTIC will get the information to display. The beginning of the display list should point to the beginning of display memory, and this pointer is always found at locations $58 and $59, SAVMSC. However, our greatly expanded display memory, over 6 times larger than normal, requires a place to reside. Therefore we'll subtract 24 pages from the high byte of the normal location of screen memory, which will give us the room we need for the display memory. The transfer of the information from location $58 to the new display list is accomplished in lines 350 to 370, and the transfer from $59 and enlargement of display memory is done in lines 380 to 420. Since we've now completed a line of the new display list, which contains an LMS instruction and an address, we decrease our line counter, Y, in line 430.

    Now we'll enter a big loop, from line 440 to line 570. The loop will be executed 23 times, and each time it will create one more line of our new display list. The first instruction placed into it is 82, as was mentioned above. Then we retrieve the low byte of the last address and add 250 to it, in lines 470 to 510. Remember to use the CLC instruction before any addition! This sets the low byte of the second line of display memory 250 bytes higher than the former line, so each line will be 250 bytes long instead of the normal 40 bytes.

    Lines 520 to 540 don't seem to do anything, do they? They add zero to a number, and replace it in memory. But remember, the carry bit is added into each ADC instruction, and we have not cleared the carry since the last addition. Therefore, if the previous addition resulted in a number larger than 255, the low address placed into the display list in line 500 will actually be the sum minus 256. However, the carry would then have been set, and it will increase the high byte of the address by 1 when we add zero. The address will then point to the correct area of memory. There's another way to code this operation:

LDA ADDR1
CLC
ADC #250
STA ADDR1
BCC PASS
INC ADDR2
PASS ....

In this case, if the carry isn't set by the first add, ADDR2 isn't incremented; but if the first sum is greater than 255, ADDR2 will be 1 higher than it was.

    We conclude the loop by decrementing our line counter, Y, again. If Y has not yet reached zero, we have more work to do, and we loop back up to do it. If Y has reached zero, we're done with this part, and we simply need to set up the JVB instruction to point to our display list on page 6. We do this in lines 580 to 670. Note lines 630 and 670. These insert the address of our new display list into locations $230 and $231, the internal pointers to the display list that the ATARI (and ANTIC) uses.

    To make the scrolling fast and smooth, we'll place our routine into the vertical blank interrupt. Our BASIC program will pass the address of the scrolling routine to the set-up routine, and lines 720 to 770 pull this address off the stack and set up the scrolling routine in the deferred vertical blank. Finally, we'll return to BASIC in line 780.

    Now that we have our display list constructed, all that we need to do is write a short machine language routine which will handle the scrolling itself. To better understand this routine, let's first discuss the mechanism of fine scrolling. A character in GRAPHICS 0 is 8 bits wide. Coarse scrolling is accomplished one character at a time; with each move, every letter on the screen appears to jump 1 position left or right. We want fine scrolling, in which each move should ideally be only 1 pixel, or 1 bit, in either direction. The ATARI lets us accomplish this fairly easily with a register called HSCROL ($D404). The corresponding vertical scroll register, which works in exactly the same way, is called VSCROL and is located at $D405.

    HSCROL can accomplish a bit-by-bit scroll of a character for 8 bits, but then it must be reset. If a zero is written to HSCROL, the position of the character is normal. If we write a 1 to HSCROL, the character shifts 1 pixel left. Writing a 2 shifts the image 1 more pixel, and so on, up to 7. At this point, we write another 0 to HSCROL, and we shift the whole character 1 whole position to the left on the screen by changing the address in the LMS instruction on each line. Pictorially, the characters shift like this:

Number written to HSCROL
    0           1            2
.......I    ......I.    .....I..
.......I    ......I.    .....I..
.......I    ......I.    .....I..
.......I    ......I.    .....I..
.......I    ......I.    .....I..
.......I    ......I.    .....I..
.......I    ......I.    .....I..
.......I    ......I.    .....I..

    After we have completed a full cycle from 0 to 7, shifting the character by 1 full position, we can start a new cycle from 0 to 7, and so on. By continuing this, we can scroll the full width of display memory. In fact, the routine we'll write below won't even check the width of memory, so it will continue to fine-scroll all the way to the top of memory if you let it run long enough. You'll get a look at the operating system of your ATARI in a new and completely unique way!

    Now that we know what we'll be doing, let's see the program:

Listing 8.5

            0100 ; ******************************
            0110 ; Set up equates and origin
            0120 ; ******************************
0000        0130        *=   $600
0600        0140 DLIST  =    $600
D404        0150 HSCROL =    $D404
E462        0160 XITVBV =    $E462
            0170 ; ******************************
            0180 ; Save accumulator and X register
            0190 ; ******************************
0600 48     0200        PHA          ; Save accumulator
0601 8A     0210        TXA          ; Transfer X register
0602 48     0220        PHA          ; And save it
            0230 ; ******************************
            0240 ; Do the fine scrolling first
            0250 ; ******************************
0603 A207   0260        LDX #7       ; 8 bits per character
0605 8E04D4 0270 LOOP   STX HSCROL   ; Scroll the 1st
0608 CA     0280        DEX          ; Set up for next scroll
0609 10FA   0290        BPL LOOP     ; Loop until 8 are done
060B A207   0300        LDX #7       ; Reset scroll register
060D 8E04D4 0310        STX HSCROL   ; To beginning
            0320 ; ******************************
            0330 ; Now we'll coarse scroll one
            0340 ; ******************************
0610 A200   0350        LDX #0       ; Counter
0612 BD0406 0360 LOOP2  LDA DLIST+4,X ; Get display memory
0615 18     0370        CLC          ; Before addition
0616 6901   0380        ADC #1       ; Raise it by 1
0618 9D0406 0390        STA DLIST+4,X ; In display list
061B BD0506 0400        LDA DLIST+5,X ; Get high byte
061E 6900   0410        ADC #0       ; Add carry in
0620 9D0506 0420        STA DLIST+5,X ; In display list
0623 E8     0430        INX          ; Move forward in
0624 E8     0440        INX          ; Display list
0625 E8     0450        INX          ; 3 bytes
0626 E048   0460        CPX #72      ; 24 * 3 = 72
0628 90E8   0470        BCC LOOP2    ; Not finished
            0480 ; ******************************
            0490 ; Now restore registers
            0500 ; ******************************
062A 68     0510        PLA          ; First, X register
062B AA     0520        TAX          ; Restored
062C 68     0530        PLA          ; Then accumulator
062D 4C62E4 0540        JMP XITVBV   ; Exit from VB

    Since this will be in the vertical blank interrupt, in lines 200 to 220 we'll save both of the registers we'll be using, the accumulator and the X register. Next, in lines 260 to 310 we'll quickly loop through all 8 bits stored into HSCROL, resetting our counter to 7 before we leave. Then in lines 350 to 470 we enter another loop, which simply goes through the display list and raises each address 1 byte, accomplishing the coarse horizontal scroll. If we were scrolling vertically, we would have to add 250 to each address, in order to coarse-scroll up 1 line here (or add 40, if we were using a normal-width display memory). In this loop, we are using the X register as a byte counter rather than as a line counter, so we must increment X 3 times for each loop (since there are 3 bytes per line of the display list).

    Finally, in lines 510 to 530, we restore the registers we saved at the beginning of the program, and in line 540 we exit to the exit routine of the deferred vertical blank.

    We can now write a very simple BASIC program to use the two routines we have written:

10 GOSUB 20000:REM Sets up string to form modified display list
20 GOSUB 30000:REM Sets up string with scrolling routine in it
30 FOR I=34000 To 40000 STEP 5:POKE I,86:NEXT I:REM Puts lines
into display memory so we can see the scroll

40 DUMMY= USR(ADR(DLSCROLL$),ADR(SCROLL$))
50 GOTO 50
20000 DIM DLSCROLL$(99):REM Length of routine to set up scrolling display list
20010 FOR I=1 TO 99:REM Length of string
20020 READ A:REM Get a byte
20030 DLSCROLL$(I,I)=CHR$(A):REM Insert it into string
20040 NEXT I:RETURN :REM All finished
20050 DATA 104,169,112,141,0,6,141,1,6,141
20060 DATA 2,6,160,24,162,3,169,82,157,0
20070 DATA 6,232,165,88,157,0,6,232,165,89
20080 DATA 56,233,24,157,0,6,232,136,169,82
20090 DATA 157,0,6,232,189,253,5,24,105,250
20100 DATA 157,0,6,232,189,253,5,105,0,157
20110 DATA 0,6,232,136,208,228,169,65,157,0
20120 DATA 6,232,169,0,157,0,6,141,48,2
20130 DATA 232,169,6,157,0,6,141,49,2,104
20140 DATA 170,104,168,169,7,32,92,228,96
30000 DIM SCROLL$(48):REM Length of routine
30010 FOR I=1 To 48:REM Get it all
30020 READ A:REM Get a byte
30030 SCROLL$(I)=CHR$(A):REM Put it into string
30040 NEXT I:RETURN :REM All done
30050 DATA 72,138,72,162,7,142,4,212,202,16
30060 DATA 250,162,7,142,4,212,162,0,189,4
30070 DATA 6,24,105,1,157,4,6,189,5,6
30080 DATA 105,0,157,5,6,232,232,232,224,72
30090 DATA 144,232,104,170,104,76,98,228

    This program first inserts the display list-creating program and the scrolling routine into strings, using the subroutines at 20000 and 30000, respectively. Line 30 simply POKES some vertical lines into our enlarged display memory so we'll have some information to scroll. Line 40 sets up the new display list, using DLSCROLL$, and passes the address of SCROLL$ to this routine so that it can be inserted into the vertical blank. Since we're not going to do anything except watch the scrolling, line 50 just keeps the real-time program running in a loop while the vertical blank interrupt program (our scrolling routine) continues to do its thing. If you watch this program run for too long, we can't be responsible for your actions – it's hypnotic!

    This concludes our review of the display list, display memory, interrupt handling and fine scrolling. You should now be able to write some fairly sophisticated routines in assembly language and use them in your BASIC programs with ease.


Return to Table of Contents | Previous Chapter | Next Chapter