MEMORY
MAP PART IV
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.
RUNSTK
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?
MEMTOP
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:
FORLN
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
110 NEXT X
120 PRINT PEEK(160)+PEEK(161)*256
LSTPNT
173,174 00AD,00AE
List pointer. Contains the location of the line being LISTed. When you just type LIST, you find 32767 here.
DATLN
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
20 READ A
30 ? PEEK(182)
40 NEXT I
50 DATA 1,2,3,4,5,6,7
20 READ A
30 ? PEEK(182)
40 NEXT I
50 DATA 1,2,3,4,5,6,7
DATALN
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
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.
STOPLN
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
110 NEXT Y
30000 PRINT PEEK(186)+PEEK(187)*256
ERRSAV
195 00C3
This location holds the number of the error that was trapped or caused the program to stop.
PTABW
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
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.
BININT
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).
Noname
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.
Noname
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.
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.
FRO
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.
FRE
218-223 00DA-00DF
This isn't very well documented, but it appears to be an extra floating point register.
FRI
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.
FR2
230-235 00E6-00EB
Floating point Register 2.
FRX
236 00EC
A single-byte register used for singlebyte calculations.
EEXP
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.
NSIGN
238 00EE
The sign of the floating point number (same suspicion as above).
ESIGN
239 00EF
The sign of the exponent in EEXP (237).
FCHRFL
240 00F0
The first character flag. Your guess is as good as mine.
DIGRT
241 00F1
The number of digits to the right of the decimal point (zero to eight).
CIX
242 00F2
An offset into the text buffer pointed to by INBUFF.
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.
ZTEMPI
245,246 00F5,00F6
A temporary register.
ZTEMP4
247,248 00F7,00F8
Another temporary register.
ZTEMP3
249,250 00179,00FA
Still another temporary register (will it never end?).
RADFLG
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.
FLPTR
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.
FPTR2
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
registers.
colors, to change character sets, even to change
player/missile positions and the fine scrolling
registers.
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.
VDSLST
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:
100
GRAPHICS 0
110 DLIST=PEEK(560)+PEEK(561)*256
120 POKE DLIST+16,130
130 FOR MEM=1536 TO 1553
140 READ INSTR
150 POKE MEM,INSTR
160 NEXT MEM
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
110 DLIST=PEEK(560)+PEEK(561)*256
120 POKE DLIST+16,130
130 FOR MEM=1536 TO 1553
140 READ INSTR
150 POKE MEM,INSTR
160 NEXT MEM
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
0604 8D0AD4 STA WSYNC
0607 8D17D0 STA COLPF1
060A ADC502 LDA COLOR1
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.
VPRCED
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).
VINTER
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.
VBREAK
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.
VKEYBD
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.
VSERIN
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.
VSEROR
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.
VSEROC
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.
VTIMRI
528,529 0210,0211
Interrupt vector for POKEY Timer 1 (see AUDF1 [53760,53761]).
VTIMR2
530,531 0212,0213
Interrupt vector for POKEY Timer 2 (see AUDF2 [53762,53763]).
VTIMR4
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.
VIMIRQ
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
picture.
takes the TV set to fill the entire screen with a
picture.
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.
VECTOR | INITIAL VALUE |
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.
CDTMVI
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.
CDTMV2
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.
CDTMV3
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.
CDTMV4
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?
CDTMV5
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.
VVBLKI
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!
changing the vector?
Crash City!
VVBLKD
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
110 READ CODE
120 POKE MEM,CODE
130 NEXT MEM
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
110 READ CODE
120 POKE MEM,CODE
130 NEXT MEM
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 |
060F 205CE4 | JSR SETVBV |
0612 60 | RTS |
0613 AD2C02 VBLANK | LDA CDTMV3 |
0616 DOOD | BNE VBLXIT |
0618 A91E | LDA #$1E |
061A 801E02 | STA CDTMV3 |
061D ADF302 | LDA CHACT |
0620 4901 | EOR #$02 |
0622 8DF302 | STA CHACT |
0625 4C5FE4 VBLXIT | JMP SYSVBV |
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.
CDTMVI
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.
CDTMA2
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?
CDTMF3
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.
SRTIMR
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.
CDTMF4
556 022C
And now, back to our timers. This is the flag for CDTMV4 (542,543). See CDTMF3 for more information.
INTEMP
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.
CDTMF5
558 022E
This is the flag for CDTMV5 (558,559). See CDTMF4 for more information (ha, ha).