Vertical Blank & Display List Interrupts
In this chapter you will learn to use Vertical Blank and Display List Interrupts. These interrupts are a powerful aid to the game programmer, who can use them to smooth animation, to enable players to be re-used in the bottom portion of the frame, to allow character sets and color registers to be changed mid-screen, and of course much more.
As you learned in the first chapter, "Vertical Blank" refers to the period of time the television set takes to reposition the electron beam back to the top left edge of the screen after raster scanning or drawing one television frame. Since television frames are generated every 1/60th of a second, there are sixty Vertical Blank periods a second, one for each frame.
Since nothing is happening on the television screen during the Vertical Blank period, it presents an ideal opportunity for a program to change the positions of objects on the screen in order to animate them. If you only knew when the Vertical Blank period occurred, you could take advantage of this time to create smooth and flicker-free animation.
The ANTIC chip in the computer lets you do just that. Since it and the GTIA/ CTIA chip generate the television image, they need to know what is happening on the screen at all times. When Vertical Blank occurs, an interrupt is generated on the Atari's 6502 CPU.
An interrupt is simply a way of telling the computer to stop what it is doing, handle something more important, then return to what it was previously doing. The 6502 checks the interrupt status after executing an instruction. If an interrupt occurred, it saves the Program Counter (marking its current place in the program) and the processor status register onto the stack. It then jumps through one of a number of preset vectors, depending on the type of interrupt that occurred. Once the interrupt has been handled, the program will RTI (return from interrupt instruction) back to the instruction that was to be executed before the interruption.
ANTIC also generates Display List Interrupts. If you recall the discussion on display lists, you remember that the display list is really a program for ANTIC. Each byte in the display list is part of a series of instructions that lets ANTIC know what type of information is to be displayed. Setting the high bit in an instruction in the display list will trigger an interrupt when it is time for the television beam to display the information for the last scan line in that graphics mode line. This type of interrupt is sometimes called a "Raster Interrupt." Remember that the interrupt does not stop the TV raster process but causes the 6502 to execute a series of instructions at this specific time.
Vertical Blank and Display List Interrupts (DLI's) are sent to the 6502 NonMaskable Interrupt (NMI) vector at $FFFA. It is called non-maskable because these interrupts cannot be disabled on the 6502 CPU as can other types of interrupts. Three interrupts go to the NMI vector. They are Vertical Blank, DLI's, and System Reset. Although these interrupts cannot be masked off on the 6502, ANTIC filters the first two. A memorymapped hardware register in ANTIC, NMIEN ($D40E), allows the programmer to enable or disable Vertical Blank and Display List Interrupts. Another register, NMIST ($D40F), shows which interrupt actually occurred. When an NMI occurs, the 6502 goes to an OS routine to find out what caused the interrupt. If a DLI occurred, this routine vectors through page-two vectors to a DLI service routine that changes one or more graphics registers which control display. On the other hand, if a VBI occurred, the OS routine saves the Accumulator, X and Y registers on the stack, then jumps through the appropriate page-two vector to the Vertical Blank routine. And if System Reset was pressed, it goes directly to the OS warm start vector for system reset ($E474).
Vertical Blank InterruptsThe Atari computers use the Vertical Blank Interrupt for many housekeeping chores. When the Atari is first powered up under normal conditions, the Vertical Blank Interrupt is enabled. A special list of vectors is placed in page two of memory. The operating system places the list of vectors in page two because these are RAM locations and the user can change their contents to point to his own interrupt service routine. System Reset is the only interrupt that cannot be routed through page two.
The operating system's Vertical Blank Interrupt (VBI) service routine is a twostage process. When an interrupt occurs, the computer is sent to a vector in the operating system, which in turn sends it to a routine to determine what type of interrupt has occurred. It then jumps through the appropriate vector on page two. It is called two-stage because it goes through two vectors on page two. This allows the programmer to use either his own VBI routine or that of the OS, or both.
The OS uses the VBI routine to transfer data from some of the special hardware registers in Atari's custom chips to shadowed locations in RAM and vice versa. For example, it reads the color register information, the location of the display list, and other graphics control information placed in RAM by the user, and writes them to their hardware addresses. It also reads many hardware registers such as game controllers and light pen and places them in RAM for the user. It updates the clock at locations 18-20 ($12-$14), and updates the timer for the attract mode which cycles the colors if a key has not been pressed for several minutes. It even takes care of the repeat action on the keyboard keys. Because the VBI routine updates many registers that are used by the average program, most programmers don't wish to replace it entirely by their own routine, since in many cases it would mean duplicating at least some of the procedures that are handled automatically by the OS.
The vectors are called Immediate VBlank and Deferred VBlank. Atari engineers split the Vertical Blank period into two separate sections because a VBI can extend for about 20,000 cycles or nearly all the time available before the next VBI. The Immediate VBlank portion refers to the time critical period when the electron beam is actually offscreen. It is here that shadowing and hardware registers are updated. Once the OS VBI routine has completed its housekeeping chores, it jumps through the Deferred VBlank vector, which is set by the OS to point to a routine that will restore the registers and return the computer to its position before the interrupt. The OS VBI routine will abort if the computer was in the middle of a time critical I/0 routine such as sending data to a cassette or disk drive. In such a case, the Deferred VBlank vector is bypassed. Unless you are using disk 1/0 where your code must be short enough not to delay shadowing updating, you can use Deferred VBlank without being concerned.
A major question asked by many programmers is: How much time do I have in the VBI routine to execute my code? To answer this we must examine the TV process again.
A television image is composed of 525 lines. Because of what is called interlacing, only half are drawn per frame, and some of these are offscreen due to normal vertical overscan. It takes the TV 63.5 microseconds to draw a single line. This includes the time it takes to shut the electron beam off and reposition it back on the left edge one scan line below. The length of Vertical Blank is equivalent to 22 scan lines or roughly 1400 microseconds. With the Atari's 6502B CPU running at 1.79 MHz, 1400 microseconds is equivalent to approximately 2500 machine cycles (1400 X 1.79 = 2506). So, how much can you do "inside" the Vertical Blank period? Not very much, if you are trying to accomplish things like moving players and scrolling the screen solely inside the Vertical Blank period. The important thing to remember is that one television frame is 1/60th of a second, which is equivalent to 16,666 microseconds or just about 30,000 machine cycles. This means that there is a maximum of approximately 30,000 machine cycles BETWEEN Vertical Blanks. ANTIC delays processor time in order to fetch screen data and look up character data during the screen drawing process. A programmer using an extensive Vertical Blank Interrupt routine will want to be sure that the routine is finished before the next VBI occurs; otherwise, his routine will be aborted in the middle unless very special precautions are taken. A programmer will also want to be sure that his VBlank routine is not so long that it causes serious delays to the program code that is running outside VBlank.
Programmers often ask if the 2500 cycle VBlank time constraint seriously limits the amount of time for smooth offscreen animation and updating. The answer is No! True, all of the graphics updating must be performed while the beam is offscreen, but you can also gain some additional time. Remember what a normal display list looks like. It begins with 24 blank lines. That gives 24 by 63.5 microseconds/line or another 2728 cycles that can be considered offscreen. High scores, player scores and messages generally cover the top few lines too. This adds additional time offscreen. Obviously if your Vertical Blank routine is even longer, you should be OK as long as you do all of your graphics updating within the first 5200 cycles of your routine. Figure that you have a maximum of 20,000 cycles (4500 instructions) in Deferred VBlank, but you must finish before the next VBI or your program will crash.
While programmers have written entire games in Deferred VBlank, only certain operations should be put in VBlank. All graphics updating, including scrolling the screen, moving player-missile objects, and changing color registers, should definitely be done in VBlank. In addition, collisions should be checked, and joysticks or paddles should be read. This is also the best place to implement time critical sound routines. Everything else, including calculations, should be in your main code outside VBlank.
Setting up a Vertical Blank Interrupt is a simple process since the OS has a routine to set up the vector for you. All it involves is storing the address of your VBI routine in the vector table on page two of RAM. If a VBI has occurred between the time the two bytes that make up the vector were stored in the table, the 6502 would jump through an erroneous address, and the program would become erroneously lost. The OS setup routine automatically assures this never happens.
Setting up the routine is simple. Load the Accumulator with 7 if you are setting a Deferred VBlank routine, and 6 if you are setting an Immediate VBlank routine. The X register is loaded with the high order byte of your routine, the Y register with the low order byte. You then JSR to the OS SETVBV routine at $E45C.
SETVBV .EQ $E45C LDA #$07 ;DEFERRED LDX /VBLANK ;HIGH BYTE OF USER ROUTINE LDY #VBLANK ;LOW BYTE JSR SETVBV
There are two possible exit points from a VBlank routine depending on whether Immediate or Deferred VBlank is used. If the programmer uses the Immediate one and still desires to use the OS VBI routine, the vector is $E45F (SYSVBV). If the Deferred VBlank is used or the programmer does not want the OS VBI routine to execute the vector, then it is $E462 (XITVBV). The XITVBV routine pulls the registers off the stack and does a RTI (Return from Interrupt).
Display List InterruptsDisplay list interrupts are generally used to change the color registers midscreen or switch character sets in use. These changes can be made very rapidly since they are short and usually modify only a few bytes. Take care so that changes of this type are offscreen during Horizontal Blank, or they will appear crude and annoying. When ANTIC encounters the DLI instruction, it completes the last scan line for the mode it is drawing, then services the interrupt. This means in effect that the interrupt must be set during the mode line above the one you want the interrupt to effect.
The period of Horizontal Blank is seven microseconds. Horizontal Overscan is another 3.31 microseconds. These 10.31 microseconds mean approximately eighteen machine cycles offscreen. In order for an interrupt routine to synchronize with Horizontal Blank, a special hardware register in ANTIC freezes the 6502 until Horizontal Blank occurs. Thi's register at $D40A is known as WSYNC. Writing to this location pulls down the ready line on the CPU until Horizontal Sync. If you insert a STA WSYNC instruction, then change the value in a color register, color won't be changed in The Middle of the current line but will go into effect when the beam is off the left edge of the screen one scan line lower.
What if eighteen cycles are not enough time to make all the changes you need? There are several approaches that can be taken, and the proper one depends of course upon the situation.
As with the VBI, you need not worry about crashing the program because the code does not fit in the Horizontal Blank time or even within the time it takes to do the entire scan line. Only if the code is so long that another DLI occurs before the previous one has finished would you be likely to run into problems and crash the program. There is no reason to believe that you must complete the interrupt routine by the end of the scan line. There are some programs that have one DLI set at the top of the screen and do not return from the interrupt until the entire screen has been drawn, hundreds of scan lines and thousands of machine cycles later. The interrupt routine is used to control graphics information to the screen, line by line. A DLI routine written in this manner is called a kernel.
Programmers using DLI's for simple screen changes should decide if all of them must be made on a single line. Perhaps only a few changes need be made immediately. The rest can be made on the next line by waiting again for Horizontal Sync. All the changes must be made on a single line, if there is a major screen division between the score line and the playfield requiring different colors and character set. In this case, it might be practical to insert a zero in the display list. A zero is the blank line instruction in a display list. Changes could be made past Horizontal Blank on this line while the raster beam is drawing it and still be invisible to the viewer.
There is one more important point for those readers who still need to count cycles. Although a horizontal line takes 63.5 microseconds, you do not have 113 cycles per line (63.5 x 1.79 = 113.6). This is because ANTIC's Direct Memory Access (DMA) ability allows it to freeze the 6502 CPU and steal cycles in order to get screen data from screen memory, player/missile data from player/missile memory, and to look up character set data. It even steals cycles to look at the display list so it knows what type of information to display. The amount of time stolen per scan line can vary. ANTIC steals a cycle for each byte in memory it must access. This DMA is controlled by a hardware register called DMACTL at $D400 (54272 decimal). The OS VBlank routine rewrites the value found at its shadow (SDMCTL) at $22F (559 decimal) every VBI. By setting bits in SDMCTL, the program can control screen width from either 128, 160, or 228 color clocks. Selecting screen width tells ANTIC it may steal cycles from the 6502 to access the display list and screen memory in order to display information. Bits are also set here so that ANTIC may steal cycles to look up player and/or missile data in memory.
If you want an idea of how much time you lose from DMA, try the following example in BASIC.
10 FOR LI=1 TO 1000:NEXT LI
10 POKE 559,0:FOR LI=1 TO 1000:NEXT LI:POKE 559,32
The second example is approximately 30% faster. The first POKE disables ANTIC's DMA, and the screen becomes black. The second POKE restores DMA after the loop is completed so we know how long it takes. If you were to try the first example in different graphic modes, you would find Graphics 8 the worst case, This is because Graphics 8 uses much more memory, so ANTIC must steal more cycles in order to access more screen memory. However, the Graphics 0 text mode isn't much faster because ANTIC also steals cycles while retrieving the characters set.
Display List Interrupts are even simpler to set up than Vertical Blank Interrupts. Since there is no DLI enabled when the program takes control, the program simply has to store the low byte-high byte address of the routine into the vector on page two (VDSLST-$200,$201). Once the vector has been stored and the display list is set for an interrupt, NMIEN is set to enable DLI's. Bit 7 of NMIEN enables DLI's. Bit 6 enables VBI's. Storing a $CO (192 decimal) is all that is needed to enable the DLI.
VDSLST .EQ $200 ;DISPLAY LIST INTERRUPT VECTOR NMIEN .EQ $D40E LDA #VBI ;LOW BYTE OF DLI ROUTINE STA VDSLST LDA /VBI ;HIGH BYTE OF DLI ROUTINE STA VDLIST+1 LDA #$CO STA NMIEN ;ENABLE DLI'S ;WITHOUT DISABLING VBI'S
Since the OS does not save the Accumulator, X and Y registers for a DLI as it does for a VBI, the user must save any registers used in the DLI routine on the stack upon entering the interrupt routine and restore them before exiting. If this is not done, the program will unquestionably crash.
The power of DLI's is obvious. With a DLI, a program can easily switch character bases from the standard ROM text set to a redefined character graphics set. Without a DLI, the programmer could only redefine characters that would not be used in the text display. DLI's enable you to change color registers mid-screen. A good example is a game like Sea Wolf (a submarine game) in which the background color changes from sky blue to ocean blue partway down the screen. Without a DLI to change the background color, one other playfield color register would have to be used for either the sky or ocean, thus limiting further the amount of color available for the screen. Multiple DLI's can be used to control screen scrolling in games like Frogger in which different bands on the screen scroll in different directions at different speeds.
DLI's can change player/missile horizontal positions and colors so players can be reused down the screen as long as vertical positions do not need to overlap in the same player. A player, which is actually a long vertical stripe, can have several different smaller images. The DLI allows you to snip the strip apart and reposition those pieces in the lower portion by changing the player's horizontal position register. If vertical movement is required, you must be careful that the different images don't cross the boundary. Since collision registers are set by the hardware when collision occurs, collision registers can be read within the interrupt routine so that hardware collision detection is not lost by reusing the players. A good example of reusing the players is the Atari Galaxian cartridge.
BASIC can use a DLI to change one or more color registers at a particular spot mid-screen, if a short Machine language routine is added. The example below changes the background color register in the text mode to orange and darkens the color of the characters so they show up against the background. This is controlled by the color in playfield 2. The change occurs in line 12 so that the interrupt is set in the mode line preceding the change. The modified display list is as follows.
112 112 Blank 24 scan lines 112 66 |LMS for BASIC 0 (ANTIC 2) (64+2) 64 |Low byte of screen memory 156 |High byte of screen memory 2 second mode line 2 2 2 2 2 2 2 2 130 Eleventh mode line + DLI (128+2) 2 2 . .
Since the display List is virtually identical, except for the modification in the eleventh mode line, we need only change that byte to activate our DLI. This is at the DLIST+15 byte.
5 REM FIND DISPLAY LIST 10 DLIST=PEEK(560)+256*PEEK(561) 15 REM INSERT INTERRUPT INSTRUCTION 20 POKE DLIST+15,130 25 REM READ IN DLI SERVICE ROUTINE 30 FOR I=O TO 19 40 READ A:POKE 1536+I,A:NEXT I 50 REM POKE IN INTERRUPT VECTOR 60 POKE 512,0:POKE 513,6 70 REM ENABLE DLI 80 POKE 54286,192 90 DATA 72,138,72,169,38,162,90 100 DATA 141,10,212,141,26,208 110 DATA 141,24,208,104,170,104,64The actual Machine language DLI service routine is as follows:
D01A: 00010 COLBK .EQ $DO1A D018: 00020 COLPF2 .EQ $D018 D40A: 00030 WSYNC .EQ $D40A 4000: 48 00040 PHA ;SAVE ACCUMULATOR 4001: 8A 00050 TXA 4002: 48 00060 PHA ;SAVE X-REGISTER 4003: A9 52 00070 LDA #$52 ;DARK COLOR FOR CHARACTERS 4005: A2 26 00080 LDX #$26 ;ORANGE BACKGROUND 4007: 8D OA D4 00090 STA WSYNC ;WAIT 40OA: 8D 1A DO 00100 STA COLBK ;STORE COLOR 40OD: 8D 18 DO 00110 STA COLPF2 ;STORE COLOR 4010: 68 00120 PLA 4011: AA 00130 TAX 4012: 68 00140 PLA 4013: 40 00150 RTI
Care must be taken when using multiple DLI's. There is only one vector for a DLI Setting NMIEN enables DLI's immediately and therefore does not wait for the next frame. Your program must insure that the proper routine will be executed. Three methods are available. The first method is to use a variable as an index that is incremented by each DLI The program then branches to the appropriate routine depending on the value of the index. The second method is to read the vertical line counter and branch to the appropriate routine. The third method is the cleanest. Each DLI routine resets the DLI vector on page two (VDSLST) to point to the next. The DLI is enabled within Vertical Blank and VDSLST is reset to point to the first DLI routine in VBlank.
The following example is a simple DLI routine to change the background color of a text screen similar to the Sea Wolf example.
00010 VDSLST EQ $200 ;DISPLAY LIST INTERRUPT VECTOR 00020 SDLSTL EQ $230 ;STARTING ADDRESS OF DISPLAY LIST 00030 WSYNC EQ $D40A 00040 NMIEN EQ $D40E 00050 COLPF2 EQ $DO18 00060 PAGEO EQ $FO 00070 LDA SDLSTL ;FIND DISPLAY LSIT 0080 STA PAGEO ;DISPLAY LIST LOW BYTE 00090 LDA SDLSTL+1 00100 STA PAGE0+1 ;SAVE HIGH BYTE 00110 LDY #$08 ;3RD TEXT LINE INA A GRAPHICS 0 DISPLAY LIST 00120 LDA (PAGEO),Y ;GET DISPLAY LSIT INSTRUCTION 00130 ORA #%10000000;TURN ON HIGH BIT ($80) 00140 STA (PAGEO),Y ;AND STORE IT BACK IN THE DISPLAY LIST 00150 LDA #DLI ;DLI LOW BYTE 00160 STA VDSLST ;STORE LOW BYTE OF OUR ROUTINE IN DLI VECTOR 00170 LDA /VBI ;GET HIGH BYTE OF OUR DLI ROUTINE 00180 STA VDSLST+l ;STORE HIGH BYTE OF VECTOR 00190 LDA #%11000000; ENABLE DLI AND KEEP VBI ($CO) 00200 STA NMIEN 00210 RTS ;AN RTS SHOULD RETURN THE PROGRAM 00220 ;BACK TO THE DEBUGGER WHEN RUN 00230 DLI PHA ;SAVE ANY REGISTER THAT WILL BE USED 00240 LDA #$A2 ;DEEP BLUE 00250 STA WSYNC ;WAIT FOR SYNC FOR NEXT LINE 00260 STA COLPF2 00270 PLA ;RESTORE ACCUMULATOR 00280 RTI ;RETURN TO MAIN PROGRAMBinary representation was used for clarity in this example.
KernelsA kernel is a special DLI routine designed to control graphics information on a line-by-line basis for the entire screen. It does this by monitoring the VCOUNT (vertical line counter) register. The most frequent use is for producing multi-colored players. Even the player width can be changed on a line-by-line basis. For example, a cowboy image could be made out of one player; a broad white hat defined at double width changes a few scan lines down to a slim pink face, then changes a few scan lines down to a brown cowboy suit and finally to black boots; four colors and two different resolutions in a single player. Another example is the players in Atari's Basketball cartridge. You cannot create multi-colored players like these without Display List Interrupts because DLI's are keyed to playfield vertical positions, not player positions.
Kernels are difficult to use because graphics information changes only during Horizontal Blank. Kernels also drastically reduce the amount of time available for program logic, since most of the 6502's time is spent writing graphics information and waiting for Horizontal Sync on the next line. Since virtually no computation time is available during display time, and only about 4000 cycles are available during Vertical Blank and overscan time, kernels are limited to simple skill and action games.
Multi-Colored Joystick Controlled PlayerThe following example illustrates the use of a kernel to produce a joystickmovable, multi-colored player. The player is 15 bytes high, but it also includes two zero bytes on each side so that it erases itself as it moves. There is no actual erase feature in this example. The player is controlled by a joystick. This is not the standard routine but an alternate one that uses table lookup to determine its horizontal and vertical movement. The formulas are as follows:
PLAYRH = PLAYRH + HOFF(STICK)
PLAYRV = PLAYRV + VOFF(STICK)
For example, if the joystick points to the right and up, STICK has the value 6. HOFF(6)=$02, and VOFF(6)=$FE or a -2. The player position changes to a location two scan lines up and two pixels right.
The kernel itself is designed to change the player's color four different times. To accomplish this it must wait for the vertical line counter to reach the player's position. Since this counter, called VCOUNT, actually increments every other scan line, we divide the player's vertical position by two. We add one because we actually want to start one line later. We have to wait for each horizontal scan to test for whether VCOUNT has reached our player's position. If it has, we then begin fetching color to put in the player's color register. Since the color changes every three scan lines, we must wait for three more horizontal scan lines before changing colors. The fact that we have to do a WSYNC for each scan line prevents us from doing any calculations outside of Vertical Blank.
Splitting Screen Horizontally Using DLIsOur second example of a kernel produces a split color screen, but this time the split is across the vertical axis with the left portion of the screen black and the right pink. The kernel takes advantage of a careful timing loop after a WSYNC instruction. It waits until the beam is offscreen for each scan line, so the beam always reaches the same horizontal position before changing the background color register to pink. The loop is a simple countdown from six to zero. The background is restored to black for the next scan line immediately after the WSYNC. Notice that the color change is fed directly to the hardware color register. Remember that the kernel operates outside of VBlank, and the shadowed background color register doesn't get copied until VBlank. Thus, color changes must feed directly to hardware. We do have a joystick-controlled player-missile routine operating in VBlank in order to show that the kernel and it are entirely independent.
Download MIDSCRN.EXE (Executable program)
Download MIDSCRN.OBJ (Object code)
Download / View MIDSCRN.LST (Assembler listing)
Download / View MIDSCRN.S (Source file)
Download / View MIDSCRN.RAW (As printed in book)
Using DLIs to Create AnimationThe third example shows a motorboat crossing water. A kernel controls an eight-scan line segment at the surface so that it appears that the water has waves. The interrupt occurs on the fourth text line. Essentially, each bit of an eight-bit random number is sequentially shifted right into the carry bit while in a loop. If the bit is set, the routine changes the background color register to light blue. If it isn't, it remains dark blue. Once the eight scan lines aye drawn, the routine sets the background color to dark blue for the remainder of the screen. A new wave pattern is picked every eight VBlanks: otherwise, the waves would change too quickly.
The white motorboat consists of two players, get side by side. It is moved in the VBlank interrupt routine which also provides the new random number every eighth cycle. The sequence is clocked through the internal clock at $14 called RTCLOC. ANDing the value with #$07 produces a positive value if you. don't have an exact even multiple of eight because some combination of the lowest three bits (1 -7) is set. In that case, the routine branches to a new random number.
There are many other possible examples using kernels. Advanced programmers might attempt to modify the second example so that a non-joystick-controlled player might have its horizontal position changed during the horizontal scan for each line. It is possible, using tight timing loops, to reuse the player on the same scan line.
Return to Table of Contents | Previous Chapter | Next Chapter