Classic Computer Magazine Archive A.N.A.L.O.G. ISSUE 66 / NOVEMBER 1988 / PAGE 45

Robin ShererMASTER

How to read the Memory Map

    Beginning users: Read the text that is printed in bold type only. These memory locations will be the easiest for you to use and usually don't involve assembly language.
    Advanced users: Read everything! Many areas of memory are not of any practical use, but you can learn a lot about how a computer works by reading the boring parts.

142,143        008E,008F

    This one is a pointer to the runtime stack. What is a "runtime stack"? Let's start off with a quick explanation of a stack.
    Ever seen a stack of trays in a cafeteria? Customers take trays off the top; cafeteria people put trays on the top. If you're not lucky, there'll be a mad rush of people, and by the time you get to the stack there will be none left, and the cafeteria people will be nowhere to be seen. Well, a computer stack is the same thing, except it uses memory locations instead of trays, and there are no cafeteria people. A special memory location is used to point to the current top of the stack.
    Now you know what a stack is, so let's talk about the runtime stack. Runtime just means that it's used while the program is running. When you use a GOSUB or a FOR/NEXT loop, BASIC has to be able to remember certain things, so it puts them on the stack until it needs to refresh its memory. Now you need to know what exactly gets put on the stack.
    For each GOSUB encountered, four bytes are put on the stack (they are taken off when BASIC RETURNs from the subroutine). The first byte is a zero and tells BASIC that this is a GOSUB. The second and third give the line number that the GOSUB was on, and the last one is an offset into the line so that BASIC knows where to continue from after the RETURN.
    FOR/NEXT loops are a little more complicated; they require 16 bytes to be put on the stack. The first six bytes give the number (in BCD) that the counter in the loop can go up to. The second six give the STEP value (also in BCD). The 13th byte gives the variable number plus 128 of the counter variable. The next two give the line number that the FOR statement was on, and the last one gives the offset within that line of the FOR. These values remain on the stack until the FOR/NEXT loop is complete.
    There is one exception to the preceding two paragraphs. A BASIC POP statement will take the top entry off the stack, be it a GOSUB for a FOR/NEXT. You should make sure you POP the stack if you have to leave a FOR/NEXT loop before it's finished or a GOSUB before the RETURN.
    Don't forget that the stack is constantly changing, so its size will vary.
    Lastly, since the beginning of the runtime stack is also the end of the string/array area, BASIC also calls it ENDSTAR. Okay?

144,145         0090,0091

    Two uses for this one. First, relevant to the last location, MEMTOP is also called TOPSTK and points to the end of the runtime stack. Since the runtime stack is the last section of memory used by your BASIC program, MEMTOP is a pointer to the end of your BASIC program (which makes sense, right?). The memory locations from the address in MEMTOP plus one, all the way up to the display list (see SDLSTL [560,561]), are free for your use (but don't forget that the value in MEMTOP will change during program execution, since the runtime stack will be growing and shrinking).
    For those of you who are still alert, don't confuse this MEMTOP with the MEMTOP at 741 and 742. This is the BASIC MEMTOP; the other is the OS MEMTOP
    The BASIC cartridge uses locations 146 to 202 for various uses, not all of which are worthwhile reporting on-with the following exceptions, of course:

160,161         00A0,00A1

    FORLN holds the line number of the current FOR statement encountered. For example:

100 FOR X=1 TO 25
110 NEXT X
120 PRINT PEEK(160)+PEEK(161)*256

173,174         00AD,00AE

List pointer. Contains the location of the line being LISTed. When you just type LIST, you find 32767 here.

182        00B6

    Points to the number of the item within the DATA statement. This means we are currently reading the first number, the second, etc. Try this program:

10 FOR 1=1 TO 8
30 ? PEEK(182)
50 DATA 1,2,3,4,5,6,7

183,184         00B7,00B8

    DATALN holds the line number of the DATA statement that was last READ. For example:

100 READ A
110 PRINT PEEK(183)+PEEK(184)*256
1000 DATA 10

    You can use DATALN in an errortrapping routine to find out where a READ error occurred.

186,187         00BA,00BB

    STOPLN holds the line number that the program was on when the program stopped, the BREAK key was pressed or an error was trapped. It is also useful in error-trapping routines. Now for our example:

100 TRAP 30000
110 NEXT Y
30000 PRINT PEEK(186)+PEEK(187)*256

195        00C3

    This location holds the number of the error that was trapped or caused the program to stop.

201         00C9

    When you print a whole bunch of items and separate them by commas in the PRINT statement (like PRINT A,B,C$), they get printed on the screen with a bunch of spaces in between them, right? Well, PTABW tells how many spaces to separate them by. In technical terms, that means it tells how many spaces there are between each tab stop on the screen (see TABMAP [675 to 689] if you want to set tabs for the TAB key). It can be set to any value from three to 255 but is initialized to ten. Let's look at an example:

100 PRINT 1,2,3
110 POKE 201,5
120 PRINT 1,2,3

    SYSTEM RESET doesn't restore PTABW to its original value; GRAPHICS doesn't; nothing does. This is a very durable location.
    Pokeing a zero here will cause the Atari to lock up shop when it encounters a comma in a PRINT statement.

202         00CA

    If you put anything other than a zero here, then going into the immediate mode (i.e., SYSTEM RESET, BREAK or the program ending) causes the program currently in memory to erase itself-yet another fun way to prevent people from looking at your program (I personally like this one; it's devious).

203-209         00CB-00D1

    These locations are free, free, free for your use if you're programming in BASIC. If you're using a different language, check the accompanying documentation to find out which page zero locations it leaves free.

210,211         00D2,00D3

    These two locations are reserved for BASIC, which means they have no specific use but you should stay away from them.

A floating point register is just a place used to
hold floating point numbers while operations are
performed on them.

The floating point package
    The remaining page zero locations from 212 to 255 are used by the OS's floating point package, a whole bunch of subroutines that BASIC uses when doing math and that kind of stuff. The routines themselves are stored in the OS ROM, so if you don't use them at all in your program, these locations will be free. Don't count on it though, even if you think you're not using the routines. They can sneak up on you when you least expect it.
    Floating point math uses six-byte BCD, which was explained briefly under location VVTP (134,135). See the section in "De Re Atari" on the floating point package for more information.
    Unfortunately, the listing for the floating point package is mighty hard to come by, so some of these locations are going to have real short explanations. My apologies to you, and my thanks to the OS Manual and Mapping the Atari for the information I couldn't find anywhere else.

212-217         00D4-00D9

    Floating point register zero. A floating point register is just a place used to hold floating point numbers while operations are performed on them (it may also hold a partial result of an operation). They are all, including FRO of course, six bytes long, since they must hold a six-byte BCD representation of the number.
    FRO is also used by the USR command. Remember that USR has the format X=USR (address [,argument][,...]) where X can be any variable and the arguments are optional. If you want your machinelanguage routine to give a value to X, you should store that value in the first two bytes of FRO (212,213 -low byte and high byte respectively) before your RTS statement. BASIC will automatically convert these bytes into a floating point number and store it in X (or whatever variable you used for the call). If you're not using BASIC, you can use FRO yourself to convert binary values to floating point and vice versa. Put the binary number in locations $D4 and $D5 and then JSR $D9AA to convert to floating point (the result will be stored in FRO). To convert back, JSR $D9D2. Note that you can't use these routines from BASIC since BASIC is constantly using FRO and will mess up your values.

218-223         00DA-00DF

    This isn't very well documented, but it appears to be an extra floating point register.

224-229         00E0-00E5

    Floating point Register 1. FR1 has the same format as FRO and is often used in conjunction with it, especially for two-number arithmetic.

230-235         00E6-00EB

    Floating point Register 2.

236        00EC

    A single-byte register used for singlebyte calculations.

237         00ED

    The value of the exponent (E). I suspect this is the exponent of the floating point number currently being processed, but this is only a suspicion.

238         00EE

    The sign of the floating point number (same suspicion as above).

239         00EF

    The sign of the exponent in EEXP (237).

240         00F0

    The first character flag. Your guess is as good as mine.

241         00F1

    The number of digits to the right of the decimal point (zero to eight).

242         00F2

    An offset into the text buffer pointed to by INBUFF.

243,244         00F3,00F4

     Finally something that can be understood! There are times when BASIC has to convert an ATASCII representation of a number to the corresponding floating point value (like when you type in X=1000). INBUFF points to a buffer used to hold the ATASCII representation. The result gets stored in FRO. See LBUFF (1408 to 1535) for the buffer itself.

245,246         00F5,00F6

    A temporary register.

247,248         00F7,00F8

    Another temporary register.

249,250         00179,00FA

    Still another temporary register (will it never end?).

251         00FB

    RADFLG determines whether the trigonometric functions (SIN, COS, etc.) are performed in radians or degrees. If it's zero, then radians are used. If it's six, then degrees are in fashion. SYSTEM RESET and NEW both restore RADFLG to radians (zero).
    BASIC also calls this location DEGFLG.

252,253         00FC,00FD

    FLPTR holds the address of the floating point number that the package is now operating on. FLPTR and FPTR2 (to follow) point to the addresses where the results of the current operation will be stored. The documentation is sketchy though, so I'm just making an educated guess.

254,255         00FE,00FF

    FPTR2 holds the address of the second floating point number that the package is operating on.

Page one
    Locations 256 to 511 are called page one and have a very important use. They make up the stack for the OS, BASIC and DOS (see RUNSTK at locations 142 and 143 for an explanation of what a stack is). On powerup (and on SYSTEM RESET), the stack pointer is set to 511. Each time a machine-language JSR or PHA (PusH Accumulator on stack) instruction is executed, data is put on the stack and the pointer moved downward accordingly. When an RTS or PLA (PuLI Accumulator from stack) is executed, the corresponding data is pulled off the stack and the pointer moved back up. Since the stack pointer (which is a special location built into the main part of the computer) is just one byte, if you try and move it below location 256, it will wrap back around to Location 511 and vice versa.

DLIs are powerful. They can be used to change
colors, to change character sets, even to change
 player/missile positions and the fine scrolling

Pages two through four
    Locations 512 to 1151, as you will see, are used by the OS as a workspace. Some are used for variables, some for tables, some for vectors, some for buffers and some just for miscellaneous stuff. Now, a few words on using these locations. Don't, unless the description says you can! A lot of them are very important to the OS, and if you mess with them, they may cause the computer to crash, which you don't want to happen. Keep in mind, though, that no matter what you do, you can't hurt the computer (unless you throw it at a wall in frustration). You'll just hurt your program.
    Also, be careful of locations that don't appear to be used. Atari has warned that these locations may be used in future versions of the OS, so stay away if you want to make sure your programs will work on all machines.
    Let's jump right into page two. The first 42 bytes are used for interrupt vectors, so we'd better take a quick look at interrupts. As you remember, we first saw interrupts at location POKMSK (16). If you don't remember, go back and reread that section. I'll wait for you here....
    Back again? Okay, so now we have the basic idea of what an interrupt is. The type of interrupt we saw at POKMSK is called an Interrupt ReQuest (IRQ). There's another kind of interrupt called a Non-Maskable Interrupt (NMI). What's the difference? Well, there's an assembly-language command called SEI (SEt Interrupt disable). It tells the 6502 (the main chip) to ignore IRQ-type interrupts. Unfortunately, it can't tell the 6502 to ignore the NMIs. They are taken care of by another chip, called ANTIC, and so ANTIC is where you must go if you want to ignore NMIs.
The NMIs consist of the Vertical Blank Interrupt (VBI), the Display List Interrupt (DLI) and the SYSTEM RESET interrupt. We'll be seeing the interrupt vectors for both IRQs and NMIs in the next few locations, along with how to use them. An interrupt vector tells the OS where to go when the corresponding interrupt occurs (assuming it hasn't been disabled).
    You might also want to look at IRQEN (53774), NMIEN (54286) and NMIST (54287) for more information on interrupts.

512,513         0200,0201

    This is the vector for the Display List Interrupt (DLI) which is an NMI, as we discussed in the last location. DLIs interrupt the screen drawing process so you can do things like change the screen color halfway down. They exist entirely for your benefit; the OS doesn't use them at all.

    To get a DLI going, there are a couple of things you have to do. First, and most important, you have to decide what you want the interrupt to do! Write the routine to do it, making sure it ends with an RTI (ReTurn from Interrupt) instruction. Next, decide which row on the screen you want it to occur at (it will actually occur at the end of this row). Go into the display list and set the leftmost bit (bit seven) of the instruction for that row. That tells the display list that there is to be a DLI on this row. Now tell the OS where the DLI routine is by setting VDSLST (low byte and high byte of the routine address). Finally, you have to enable the DLIs. Do this by setting NMIEN (54286) to 192.
    Here's a quick example from BASIC, simply reversing the playfield colors halfway down the screen:

110 DLIST=PEEK(560)+PEEK(561)*256
120 POKE DLIST+16,130
130 FOR MEM=1536 TO 1553
170 POKE 512,0:POKE 513,6:POKE 54286,192
180 LIST
190 DATA 72,173,198,2,141,10,212,141,23,208
200 DATA 173,197,2,141,24,208,104,64

    Make sure that the DATA is correct before you run the program. If it isn't, the computer might lock up. Here's an assembly listing of what those DATA statements represent:

0600 48     PHA
0601 ADC602 LDA COLOR2
0607 8D17D0 STA COLPF1
060D 8D18D0 STA COLPF2
0610 68     PLA
0611 40     RTI

    Now that you know the basics, let me tell you a few limitations. First of all, there is very little time available during a DLI before the next row starts to get drawn. Make your routine short. Second, because an interrupt often occurs while something else is going on (like your BASIC program running), you have to make sure that you restore the accumulator and the X and Y registers if you use them. Do this by pushing their values onto the stack before you use them and then pulling the values back off before you RTI. Finally, as should be painfully obvious to you BASIC programmers by now, this is most definitely machine-language country. It's not very difficult machine language, but it is machine language.

    A few notes now for the machine-language programmers. Change the hardware registers, not the shadow registers. The shadow registers are used to update the hardware registers during VBLANK. Changing them halfway down the screen won't have any effect until VBLANK kicks in.

    If you're going to have more than one DLI, then each DLI routine will have to reload VDSLST to point to the next one. The last one will have to point back to the first one. Make sure in this case that you enable the DLIs during VBLANK, or else they may not execute in the right order.

    Use WSYNC (54282) if you're changing screen colors. When any value is stored in WSYNC, the next command won't be executed until the TV has finished drawing the current scan line. If you don't use it, your colors will change in the middle of a line and will flicker back and forth. Try it and see for yourself (get rid of "141,10,212" in Line 190 and change "1553" in Line 130 to "1550").
    One other problem with DLIs is that pressing a key on the keyboard can cause DLI colors to "jump" down a scan line (try it). The solution? Well, the easiest is just not to use the keyboard. For more complex ways around it, you should consult "De Re Atari."
    DLIs are extremely powerful. They can be used to change colors, to change character sets, even to change player/missile positions and the fine scrolling registers; so be creative. Proper use of DLIs can produce a program that will do things you never thought the Atari was capable of.

514,515         0202,0203

    This one's an IRQ vector, for an interrupt called the "serial proceed line interrupt," where the word "serial" indicates I/O to a peripheral such as the disk drive. It is initialized to 59314, which just holds a PLA and an RTI (i.e., the interrupt is used).

516,517         0204,0205

    Another IRQ, this time for the "serial bus I/O interrupt." Initialized to 59314 again because it isn't normally used. Both VINTER and VPRCED's interrupts are processed by the PIA (Peripheral Interface Adapter) chip.

518,519         0206,0207

    IRQ again, for the machine-language BRK command [which is not the same as the BREAK key; see POKMSK (16) and BRKKEY (17)]. It's also initialized to 59314.

520,521         0208,0209

    From now on, if I don't tell you what kind of interrupt it is, it's an IRQ, okay? There's a whole bunch of these suckers and only so many ways to say "here's another IRQ."
    So here's another IRQ. This one occurs whenever a key other than BREAK is pressed (START, OPTION and SELECT don't count because they're buttons, not keys). It's initialized to 65470, which is the OS keyboard IRQ routine (it makes sure that only one character gets printed when you press a key, and resets ATRACT [77]). If you want to put your own routine in, this is the place to do it. Keep in mind, however, that your routine will be executed before the key code gets converted to ATASCII (see the OS manual for a list of key codes).
    The following three vectors are used to control communication between the serial bus and the serial bus devices (serial refers to the fact that bits are sent or received one after the other in succession). A much simplified explanation of this process follows. You should consult "De Re Atari" if you need more details.
    The data being sent or received is stored in a buffer. If we're doing output, then a byte gets transferred from the buffer over to the serial output register (an interrupt routine does this). SIO takes it from there and puts it in POKEY's serial output shift register. POKEY then picks it up and sends it out one bit at a time. An interrupt is then generated, and the whole process starts over. This goes on until the checksum byte has been sent, at which time a "transmit done" interrupt is generated and SIO hands control back to the main program, which has been waiting patiently all this time.
    The process is pretty much the same if we're receiving data, except in reverse.

522,523         020A,020B

    This is a good one. The "POKEY serial I/O bus receive data ready" interrupt vector. It means that this vector is used when the I/O bus indicates that it has received a byte that is now waiting in the serial input register, ready to be moved to a buffer. The routine in the OS to do this is at 60177, and that's what VSERIN is initialized to.
    VSERIN is also called INTRVEC by DOS, which changes its value to 6691, a routine in DOS that does pretty much the same thing as the one in the OS, except in a different place.

524,525         020C,020D

    The opposite of VSERIN, VSEROR is used when the I/O bus is ready to send a byte. Its official name is the "POKEY serial I/O bus transmit data ready" interrupt vector, which should make more sense this time. It is initialized to 60048, the address of an OS routine that, logically, moves the next byte in the buffer to the serial output register (from whence it gets sent). DOS messes with this one too, changing it to 6691, the address of its routine to do the same thing.

526,527         020E,020F

    Another long-winded name: the "POKEY serial I/O bus transmit complete" interrupt vector. Since I'm sure you're all becoming experts at interpreting these names, it should come as no surprise that this vector is used when all the data has been sent. It is initialized to 60113, a routine that, when the checksum byte is sent (see CHKSUM [49]), sets the "transmission" done flag at XMTDON (58) and disables this kind of interrupt.
    The following three locations are the interrupt vectors for the POKEY timers, all of which are initially unused and therefore set to the PLA/RTI combination at location 59314. The timer interrupt occurs when the associated timer counts down to zero.
    For more information on the POKEY timers, see the section on timers right before location 53760.

528,529         0210,0211

    Interrupt vector for POKEY Timer 1 (see AUDF1 [53760,53761]).

530,531        0212,0213

    Interrupt vector for POKEY Timer 2 (see AUDF2 [53762,53763]).

532,533         0214,0215

    Interrupt vector for POKEY Timer 4 (see AUDF4 [53766,53767]). This vector only exists in the "B" version of the OS.

534,535         0216,0217

    Every IRQ vectors through this location on its way to the individual interrupt routines. It is initialized to 59126, the address of an OS routine that looks at IRQST (53774) to determine what kind of interrupt occurred and then jumps through the appropriate vector.

Attention B OS owners!
    Since a lot of addresses in the new "B" version of the OS got shifted around, some of the initialization addresses given aren't the same in that version (which is now in a majority of the Ataris out there). Here are the changes (Figure 9).

A jiffy is 1/60 of a second: that's the time it
takes the TV set to fill the entire screen with a

Software timers
    There are two types of timers in the Atari: software and hardware. We've already come across the hardware timers (see VTIMRI-4 [528-533]), and we're about to learn everything we never wanted to know about the software timers, which use Locations 536 to 558. But first, a few words from our author.
    There are, of course, differences between software and hardware timers, and you'll probably want to know them before you go running off into timer land. The biggest difference comes from the names.

VDSLST 59280
VPRCED 59279
VINTER 59279
VBREAK 59279
VKEYBD same as before
VSERIN 60175
VSEROR same as before
VSEROC 60111
VTIMRI-4 59279
VIMIRQ 59142
VVBLKI 59310
VVBLKD 59653

    Hardware timers are built into the POKEY chip; software timers are part of RAM. The big difference comes in the way they keep time. You recall from location RTCLOK (18-20) that a jiffy is 1/60 of a second, the amount of time it takes the television set to fill the screen. Well, the software timers count down by one every jiffy. The hardware timers, on the other hand, count down by an amount less than  a jiffy, which you can specify (see Locations 53760 through 53769). So, if you want to time things that take longer than a jiffy, use the software timers. Otherwise, go for the hardware.

536,537         0218,0219

    This is the first software timer (affectionately known as "System Timer 1"). Every VBLANK, the value in CDTMVI gets decremented by one. When it reaches zero, a flag gets set so the OS knows to JSR through CDTMVI (550,551). An important thing to note here is that the decrementing for this timer (and only this timer) is done during Stage 1 VBLANK. This means that CDTMVI (along with RTCLOK [18-20] and ATRACT [77]) is updated every VBLANK, no matter what's going on elsewhere in the computer. The rest of the software timers, on the other hand, are updated during Stage 2, which means that during time-critical I/O (like disk and cassette I/O; see CRITIC [66]), the other times are not updated. Unfortunately, the OS knows this too, so it uses CDTMVI for I/O routines. So, you see, we have a catch-22 situation here. Oh, well! If you're doing your own time-critical routines though, you know which timer to use.

538,539         021A,021B

    This is System Timer 2, of course. When it reaches zero, it JSR's through CDTMA2 (552,553). And, unless you slept through the last paragraph, you should already know that it will not be updated during time-critical I/O.

540,541         021C,021D

    The third system timer, again hampered by time-critical I/O. This one has problems of its own through. First of all, the cassette handler uses it. Secondly, instead of JSRing through a vector when it gets down to zero, it just clears a flag at CDTMF3 (554). So don't use it during cassette operations and don't expect it to go anywhere after it's done.

542,543         021E,021F

    Let's see. You've already figured out that this is System Timer 4, that it doesn't work during time-critical I/O and you may have guessed that it clears a flag at CDTMF4 (556) when it's done instead of vectoring. What's left for me to say?

544,545         0220,0221

    The last of the timers. This one is no different than the last one except that the flag it clears is at CDTMF5 (558). But since you're getting to know these things so well, I shouldn't have to tell you that.

546,547         0222,0223

    Since this is the vector for the VBLANK Interrupt (VBI), I suppose this is probably a good time to explain exactly what vertical blank is. With all the previous mentions of jiffies in this book, you should know by now that a jiffy is 1/60 of a second. It is important because that's the time it takes the television set to fill the whole screen with a picture. Since the screen can't hold on to that picture for very long, the TV keeps drawing the picture over and over again, even if it doesn't change. It draws it one line at a time, from top to bottom. When it gets to the bottom, it stops drawing and goes back to the top, where it starts all over again. Now, the important part for us is when it stops drawing. At that time it tells the computer, "Hey, I'm not drawing to the screen anymore," thus generating a vertical blank interrupt. You should be able to see where the name comes from now. Incidentally, there is also a horizontal blank, which occurs while the TV has finished drawing one line and is on its way to the beginning of the next. Store any value in WSYMC (54282) and the computer won't do anything until the next HBLANK occurs.
    Back to VBLANK. There are a few reasons why the TV isn't drawing to the screen. First of all, it gives us a way to time things, since VBLANK occurs precisely every 1/60 of a second. Secondly, nothing is being drawn to the screen during this time, so any graphics changes made during VBLANK will result in smooth, instantaneous changes on the screen. But, perhaps most importantly, VBI code runs independently of mainline code. What does that mean? It means that VBI code is essentially a separate program, running at the same time as your regular program! I wrote one VBI program, for example, that allowed the computer to play music at the same time I was typing in programs. Chris Crawford, in his classic Eastern Front 1941 game, used VBI to separate the thinking process of the game from the tedious stuff like graphics and user input. That allowed the computer to think about its next move at the same time the player was thinking about his or hers, thus simulating a true one-on-one situation. As you can see, VBLANK is an extremely powerful tool.
    Let's take a closer look at what normally goes on during VBI. First of all, there are two stages. The first stage is always executed, while the second gets ignored if the timevertical I/O flag at CRITIC (66) is set. The first is called "immediate" vertical blank, the second is "deferred."
    VVBLKI is the vector for the immediate stage, so the OS goes through VVBLKI when the VBLANK interrupt first occurs. During this stage the real-time clock (RTCLOK [18-20]), the attract mode (ATRACT [77], DRKMSK [78] and COLRSH [79]), and system timer one (CDTMVI [536, 537]) get updated, processed and so forth. Then CRITIC is checked. If it's set, indicating that the interrupt occurred in the middle of a time-critical I/O operation, the OS returns from the interrupt. If it's not, then it's okay to go on to Stage 2, so we do. When the OS is done with Stage 2, it vectors through VVBLKD (548,549) to the user's deferred VBI routine, and then finally returns from the interrupt when it's done there.
    VVBLKI is initialized to point to SYSVBV (58463), which contains a JMP instruction to the OS Stage 1 code (located at 59345 in the old OS, 59310 in the new one). If you change VVBLKI to point to your own routine, and you still want the OS code to be executed, you should end your routine with a JMP SYSVBV statement.
    Whew, what a lot of mumbo jumbo! If you managed to plod through all of that, take a well-deserved rest. When you're done, we'll take a look at how you can use vertical blank for your own routines.

What happens if a VBI occurs while you're
changing the vector?
Crash City!

548,549         0224,0225

    Don't worry, there's still more to come on VBIs! This just seemed like a good time to formally introduce VVBLKD, the vector for the user's deferred VBI routine. The OS initializes VVBLKD to its "exit vertical blank" routine (at 59710 in the old OS, 59653 in the new one). If you use VVBLKD to point to your own routine, make sure to end that routine with a JMP XITVBL (XITVBL contains a JMP instruction to the exit vertical blank routine, which means you don't have to worry about which OS is being used since XITVBL is at 58466 in both). Note that you can also avoid the whole entire OS VBI code by writing your own immediate VBLANK routine and ending it with a JMP XITVBL instead of a JMP SYSVBV. Remember that none of the timers or color registers or anything will be updated if you do this (unless you update them in your routine).
    By now you're probably either real excited over the prospect of using VBIs yourself, or you're asleep. If it's the latter, then you're not even reading this because your eyes are closed, so I'm only going to deal with those of you who are excited, okay? Let's look at how to write our own VBLANK routines.
    The first step is to decide whether you want your routine to be immediate or deferred. Most of the time it doesn't matter. There are, however, the following conditions which will require one over the other.
    1. If you want to change locations that the OS deferred routine also changes, you obviously want to do so after the OS does. Use deferred.
    2. The maximum amount of time you can spend in immediate VBI is 2,000 machine cycles (see a book on 6502 assembly language for information on the number of machine cycles per instruction). If your routine is going to be long, you should therefore put it in deferred VBI, which has 20,000 cycles available. If you don't, things are going to look mighty funny on the screen. If you do use deferred, do your graphics first, since some of those 20,000 cycles occur while the screen is being drawn.
    3. If you need your routine to be executed every VBLANK, regardless of whether timecritical I/O is occurring, use immediate. Be careful, however, that your routine will not cause problems with the I/O.
    Now that you've decided what it should be (and you've presumably written it and put it in memory somewhere), all you need to do is change VVBLKI or VVBLKD to point to it. A simple task, right? Not quite. What happens if a VBI occurs while you're changing the vector? Crash city!
    To make sure this doesn't happen, you have to change the vectors during VBLANK. But that itself presents a small problem. How do we get into VBLANK to change the vectors if we have to change the vectors to get to VBLANK (good old catch-22 again)? Luckily, Atari has thoughtfully provided a VBI routine that makes the change for you. It's called SETVBV and is at 58460. To use it, load the 6502 Y register (LDY) with the low byte of the address for your routine, and load the X register (LDX) with the high byte. Then load the accumulator (LDA) with a six if you want immediate VBI, seven if you want deferred, and JSR SETVBV. Now your VBI will be up and running.
    Here's a simple example that uses location Chact (755) to make inverse text blink:

100 FOR MEM=1536 TO 1575
140 X=USR(1536)
150 DATA 104,169,0,141,29,2,160,16,162,6,169,6,141,29,2,32
160 DATA 92,228,96,173,28,2,208,13,169,30,141,28,2,173
170 DATA 243,2,73,2,141,243,2,76,95, 228

    Make sure that the DATA values are correct before you run the program. If they aren't, the computer will probably crash and you'll lose the program.
    Here's the assembly-language listing of the machine code (which is stored in the DATA statements):

0600 68 PLA
0601 A900 LDA #$00
0603 801D02 STA CDTMV3+1
0606 A010 LDY #VBLANK&255
0608 A206 LDX #VBLANK/256
060A A906 LDA #$06
060C 8D1D02 STA CDTMV3
0612 60 RTS
0618 A91E LDA #$1E
061A 801E02 STA CDTMV3
0620 4901 EOR #$02
0622 8DF302 STA CHACT

    The "LDA #$IE" in the preceding listing is used to specify a half-second interval ($lE hex equals 30 decimal equals 30 jiffies equals half a second) for use in blinking. Make it  larger or smaller to make the interval longer or shorter, respectively.

550,551         0226,0227

    CDTMVI is the vector for System Timer 1 (CDTMVI [536,537]). It's initialized to 60400, which is the address of a routine to set the time-out flag TIMFLG (791). This is because the OS uses CDTMVI for I/O routines, which is a very good reason why you probably should use Timer 2 instead.
    The OS vectors through CDTMVI when CDTMVI counts down to zero. If you do use CDTMVI and are setting it for a value greater than 255 (i.e., setting both the low and high byte), this presents a potential problem. Since CDTMVI is updated during VBLANK, and there is a chance that a VBLANK might occur while you're setting CDTMVI, you should set the low byte first. You can also use the SETVBV routine mentioned in the VBLANK description preceding. Just LDY with the low byte, LDX with the high, LDA with the timer number (1-5), and JSR SETVBV This will assure that the timer gets set during VBLANK.
    Since the OS JSRs through this vector, you should end your routine with an RTS instruction.
    Incidentally, CDTMVI reaching zero generates an NMI, which then does the vector.

552,553         0228,0229

    Same as CDTMVI, except this one is not used by the OS and is therefore initialized to zero. Oh, and of course CDTMV2 (538,539) reaching zero causes the vector through here, not CDTMVI. But then we already knew that, didn't we?

554         022A

    Unlike system Timers 1 and 2, Timers 3 through 5 merely clear a flag when they count down to zero. This is the flag for CDTMV3 (540,541) and is also used by DOS as a timeout flag, so beware of possible conflicts if you use it.
    As with the other two flags, you must set CDTMF3 when you set CDTMV3. Any nonzero value is okay.

555         022B

    Well, here in the middle of all the timer stuff is a different kind of timer. As everybody knows, if you hold down a key on the Atari, it will start repeating, right? And something has to tell the OS how long to wait before starting that repeat and before repeating it again, right? And can you guess what location does that? Sure, I knew you could. SRTIMR is set to 48 every time a key is pressed. Every Stage 2 VBLANK that the key is still held down, SRTIMR gets decremented by one. When it reaches zero, the repeat process starts. It gets set to six, decremented again, the key repeats, it gets reset to six, and so forth until the key is released. Unfortunately, there are no locations that store the two delay times, so you can't speed up or slow down the process just by changing a couple of locations. There is, however, another way to do it.
    As you recall, the initial delay time of 48 is set whenever a key is pressed. As you may or may not recall, we came across a vector a few locations ago (VKEYBD [520,521]) that pointed to the IRQ routine for a key being pressed. It is in this routine that the delay is set. So in order to change the delay, you must essentially take the OS routine, change the delay value, store your revised version in memory and update the vector. You'll find the OS routine at location $FFBE on page 130 of the OS listing.
    How about the other delay, the six jiffy one, once the repeat is started? If you were paying attention (and I know you were), you already know that it gets set in Stage 2 VBLANK. Can you guess what you're going to have to do to be able to set it yourself? If you guessed "take the OS Stage 2 VBLANK interrupt routine and put it in my own deferred VBI routine with the delay value changed," then give yourself a pat on the'back.
    "But wait! The OS Stage 2 VBI routine gets executed whether I have my own deferred VBI routine or not," you say, taking me completely by surprise. You're right, though (or would have been if you had said it). Your deferred routine, however, happens after the OSs, so you can just repeat the part that sets the delay and, since you'll set it after the OS does, yours will be the one that counts. The part you want is at locations $E87C through $E897 on page 36 of the OS listing, and locations $E8E8 through $E8EE on page 37 (these locations will be different in the new OS, but that's irrelevant here). Be aware that the OS will now be executing this routine twice and will therefore be decrementing by two every VBLANK. You should set SRTIMR to double the delay you want, and also change your deferred routine so that it resets SRTIMR if it's equal to zero or one. That makes sure that the OS routine doesn't reset it before you get a chance to.

556         022C

    And now, back to our timers. This is the flag for CDTMV4 (542,543). See CDTMF3 for more information.

557         022D

    INTEMP is used for temporary storage during the SETVBL routine. As you recall, SETVBL is at the address stored in 58460. Heaven only knows what INTEMP is doing here in the middle of the system timers.

558         022E
    This is the flag for CDTMV5 (558,559). See CDTMF4 for more information (ha, ha).