Master Memory Map
by Robin Sherer
How to read the Memory MapBeginning 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.
The information is formatted like this:
DECIMAL # HEXADECIMAL #
DECIMAL # HEXADECIMAL #
HEXadecimal numbers are often preceded by a "$".
Page ZeroLocations 0 to 255 are called "Page Zero" (in the language of computers, a "page" is 256 bytes). Since one byte can hold any number in the range of 0 to 255, the computer only needs one byte to hold the address of a page zero location. This saves time when you have to load or store a value in machine language so page zero is very important to machine-language programs that have to run as quickly as possible. That's why the operating system uses the first 128 bytes. The other 128, locations 128 through 255, can be used by BASIC and you for superfast machine code.
Machine-language - programmers should note that Locations 2 through 7 are not cleared by either a coldstart (turning the computer off and then on again) or warmstart (pressing SYSTEM RESET) operation.
The great mystery location! Nobody seems to know exactly what this location does. According to Atari's operating system listing, it is "LINBUG RAM [and] will be replaced by [the] monitor RAM" (your guess is as good as mine), and the only time it uses it is to define it. It does seem to be used to store the VBLANK timevalue though, so it's probably not completely useless.
This is used in "cassette initialization:' As you probably already know, if you hold down the Start button while turning on the computer, the computer will beep. If you then press RETURN, the computer will expect a machine-language tape to be in the cassette recorder and will proceed to load it. This process is called "booting" a cassette. The first six bytes stored on the machine-language tape contain special information about the tape. The first byte, actually, is ignored. The second tells how many 128 byte "records" are on the tape (when you load in the tape, each beep while loading represents a record). The third and fourth give the starting address that the machine code is to be stored at, called the "load" address. The fifth and sixth give the "initialization" address (where to go to get the program set up and ready to run). The initialization address, as you may have guessed, gets stored here at CASINI. Once the whole program has loaded, the computer jumps to the load address plus six (to skip over these special bytes) where the program either tells it to load some more or RTS (ReTurn from Subroutine). When the computer comes across an RTS instruction, it looks in CASINI for the initialization address and JSRs (Jump to SubRoutine) to that address. Finally (and you thought cassette boots were easy), the computer JSRs to the address in DOSVEC (10,11), which gets the program running (DOSVEC should be set up by the program either in the initialization process or as part of a multiple load).
RAMLO has a bunch of uses, none of' Which will be useful to you. First, the OS uses it as an index (like the variable in a FOR/NEXT loop) while clearing out memory after you turn on the computer. It also uses it. as an index while testing memory to make sure everything is A-okay. Finally, and you'll love this one, it's used to store the "disk boot address," which is usually 1798 in case you care, for the boot continuation routine (which is what happens when you want to load into more than one part of memory). By the way, it's real buddy-buddy with TRAMSZ and TSTDAT (the three work together in the RAM test routine).
Another location with a whole bunch of uses. As mentioned, TRAMSZ helps out RAMLO in testing the RAM. Its value is then transferred to RAMTOP (location 106). But, before any of that happens, it is used in testing whether or not a left cartridge is plugged in. If there is a left cartridge (also known as cartridge A), then TRAMSZ is set to one. If not, it's set to zero.
This one only has two functions. First, as you already know, it helps out in the RAM test routine (see your OS listing if you're dying to find out what the RAM test routine is). Secondly, like TRAMSZ., it's used initially in testing whether or not the right or B cartridge is present.
Machine-language programmers: Locations 8 through 15 are cleared on coldstart only.
This is the warmstart flag, telling you whether you're in the middle of a warnrstart or a coldstart. If WARMST equals 0, then you're in the middle of a coldstart. If it's anything else, then you're in the middle of a warmstart (pressing SYSTEM RESET will set WARMST to 255). The main purpose of- WARMST is to make sure that if-someone presses the SYSTEM RESET button before everything is initialized properly, the computer will know about it and start over instead of messing eventhing up. Nice stuff to know. but generally useless. But wait, you say, can't I trick the computer into rebooting by changing the value of WARMSTART to 0, that way preventing people from using SYSTEM RESET to stop by BASIC program so they can LIST it? No. Although you can change the value to 0, as soon as you press SYSTEM RESET it will change back to 255. See Location COLDST (580) for a way that Nciu can trick the computer. You miglit also look at locations POKMSK (16) and STMCUR(138,139) for other ways to protect your BASIC programs from other people's greedy eyes.
Incidetally, Warmstart normally starts at location 58484.
Booting, as you will recall, is the process of loading the program into the computer's memory. In our case the program is loaded from tape or disk. Sometimes a boot is not successful. Maybe you put a rock 'n' roll tape into your Atari recorder by mistake, or you forget to close the disk-drive decor. In any case, BOOT? is used to tell the operating system whether or not the boot attempt was successful. If BOOT? is equal to one, then there was a successful disk boot. A two indicates a disk boot, and a three (a one plus the two) means both the disk and tape booted. A zero means that everything bit the big one.
If a cassette boot attempt doesn't work, then the OS goes on as though there were no attempt. If the disk boot attempt fails, and this has happened to most of us, then a lovely "BOOT ERROR" message appears on the screen and the OS gives it another try.
Okay, now for some miscellaneous stuff. A cassette boot always comes before a disk one. If there is a successful cassette boot, then every time SYSTEM RESET is pressed the computer will go to the address stored in CASINI.
The address is a location where a routine you want to use is located in memory. This address is usually called a "vector," because it points to something. You can JSR in machine language or USR in BASIC to get to the routine.
Back to CASINI. If the disk boots successfully, then the computer will go to the address stored in DOSVEC (10,11). If BOOT? is set to 255 by you, then the computer will "lockup" if SYSTEM RESET is pressed. This is a great way to keep people from looking at your programs. Incidentally, "lockup" means that the computer will not do anything until you turn it off.
10,11 000A, 000B
This is another vector, used to tell the OS what to do when SYSTEM RESET is pressed. It holds the cassette-boot starting address, the disk-boot starting address, or the address of the "blackboard mode" routine (type "BYE" from BASIC and press RETURN; that's the blackboard mode and the routine for it starts at location 58481). It's called DOSVEC, because if you're using DOS from BASIC, DOSVEC holds the address that BASIC jumps to when you call DOS (DOSVECtor-get it?). If you want to use this location from BASIC to point it to your own routine, then you'll have to make a small change to DOS, since in this case SYSTEM RESET restores DOSVEC to its original value. The change is easy to make, though. All you have to do is POKE 5446 with the Least Significant Byte (LSB) of the address of your routine, and 5447 with the Most Significant Byte (MSB) of the address. The MSB is the first two digits of the hex address, the LSB is the last two. You can compute MSB and LSB from a decimal address with the following formulas: MSB = INT (address/256), LSB =address - (256*MSB). Then call DOS and resave it using the WRITE DOS FILES option. This will give you a custom version of DOS that will allow your routine to run every time SYSTEM RESET is pressed or DOS is called.
Miscellaneous stuff again; DOSVEC is set to 6047, the address of a routine to load in the DUPSYS file, if DOS is used and it is not told otherwise (i.e., no user boot programs). And, for you machinelanguage dabblers, if you create an AUTORUN.SYS file that doesn't end with an RTS, make sure you set BOOT? to 1 and COLDST (580) to 0 (so as not to confuse the computer).
12,13 000C, 000D
This one's easy. Essentially, it is the disk equivalent of CASINI. As a matter of fact, the cassette initialization address is stored here before the OS realizes it's doing a cassette boot and moves it to CASINI. If there is no cassette or disk boot, DOSINI will read 0, 0.
DOSINI can be very useful because it holds the address that the OS jumps to when SYSTEM RESET is pressed. If you have a machine-language routine that you want to go to whenever SYSTEM RESET is pressed, store its address here.
14,15 000E, 000F
This location helps prevent your programs from accidentally being written over by the OS. If you're using BASIC, it points to the end of your BASIC program. The OS uses it to determine whether or not there's room for the graphics mode you want to use. As you probably know, the graphics mode stuff (screen memory and display list) is stuck way up at the top of memory. When you tell the OS to set up a graphics mode (with either a GRAPHICS or OPEN "S:" command), it tries to put the display list and screen memory right below the top of memory. Unfortunately, sometimes there isn't enough room, and they would extend down into your program, which you obviously don't want to happen (unless it's a horrible program). APPMHI to the rescue! Before it sets up the requested graphics mode, the OS checks APPMHI to see if there's enough room. If there isn't, it tells you so and sets up a GRAPHICS 0 screen instead, updating MEMTOP (741,742) in the process. MEMTOP, in case you didn't guess, holds the address of the last possible memory location you can use for your program, i.e., the memory location right before the display list. On the other hand, if there is enough room, the desired mode will be set up and MEMTOP updated accordingly.
Sometimes you may want to use the memory between the end of your program and MEMTOP to store character sets or player/missile information. That's fine, but make sure you change APPMHI so that the OS knows that you're using that memory (in other words, set APPMHI to point to the memory address after the last one you use).
Other locations that might be of interest here are CHBASE (54281), PMBASE (54279) and RAMTOP (106).
Machine-language programmers: Locations 16 through 127 are cleared on either coldstart or warmstart.
POKMSK is used to turn various types of "interrupts" on or off. An interrupt is exactly what it sounds like; the computer gets interrupted from whatever it's doing and is told to do something else (it then usually returns to what it was doing before it was so rudely interrupted).
For machine-language programmers, POKMSK deals with POKEY interrupts and is used and altered by the IRQ service. It's also a shadow register for IRQEN (53774).
The following chart (Figure 3) shows exactly what part of POKMSK deals with which interrupts. Change a specific bit to a one to turn on that interrupt, zero to turn it off.
Before we decide whether or not any of this is useful, a few notes for the diehards. The default value for POKMSK is 192, BREAK key and "other key" interrupts enabled. When you enable a timer interrupt, the associated AUDF register will be used as a timer and will generate an interrupt request (IRQ) when it has counted down to zero. See VTIMR1/2/4 (528 to 535) and the POKEY chip (53760 to 54015) for more details.
For you beginners, as well as the pros, there is a handy-dandy use for POKMSK. If you haven't guessed already it allows you to disable the BREAK key so that nobody can BREAK into your program and steal your code. All you have to do is turn bit seven off. How do you do that? Try the following subroutine:
1000 BK=PEEK(16):IF BK>128 T
HEN POKE 16,BK-128:POKE53774
Notice that we also change Location 53774. As mentioned before, POKMSK is a shadow register for 53774, and therefore both must be changed. We also check first to make sure that bit seven is on. We do this because, unfortunately, this routine has to be called more than once. You see, the BREAK key is re-enabled by the first PRINT statement that prints to the screen, by an OPEN "S:" or OPEN "E:" statement, by the first PRINT statement after such an OPEN, by the first PRINT statement after a GRAPHICS command, or by a SYSTEM RESET Phew! To make sure you keep the BREAK key disabled, you'll want to GOSUB to the preceding routine after each such command.
More for the machine-language programmer. If you have the newer OS `B' ROM, there is a vector for the BREAK key interrupt that allows you to write your own routine for the BREAK key. It is called BRKKEY, and can be found at locations 566 and 567.
Okay, you've used POKMSK to zonk out the BREAK key. What happens if for some reason you need to know if somebody's pressing it? BRKKEY tells you just that. If it's equal to zero then the BREAK key is pressed (if it's not then it isn't!). If you're looking at BRKKEY from BASIC, remember that you'll have to keep checking it over and over again; BRKKEY tells if BREAK is pressed, not if it were
Machine-language programmers, this location along with POKMSK lets you write your own BREAK key routines if you don't have the `B' ROM, or if you want to make sure your software will work on the old ROMs. If you do have the 'B' ROM (location 58383 will equal zero if you do), you can use the vector mentioned under POKMSK.
A few boring bits if information. If the BREAK key is pressed during an input/output (I/O) opertion, BRKKEY will read 128, not 0. The keyboard, display, screen, and cassette handlers all check BRKKEY to see if they should BREAK (why else?), as do I/O routines and scroll and draw routines. Also look at locations STATUS (48) and DSTAT (76) for related stuff.
This one's actually fun and interesting, and you may even have used it already. It's a clock-the "internal real-time clock" (which just means that it's inside the machine and actually keeps good time). It doesn't count in seconds though, but rather Jiffies:' A jiffy is 1/60 of a second, which happens, not by coincidence, to be the time that it takes the television to fill the screen. After the screen is filled, a special interrupt occurs, called the Vertical BLANK (VBLANK) interrupt. The OS gets a lot of things done during VBLANK, one of which is updating RTCLOK. Every jiffy (during VBLANK), Location 20 gets increased by 1 until it equals 255. At that point, since 255 is the largest number a memory location can hold, it gets reset to 0 during the next VBLANK, and Location 19 gets increased by 1. You can probably guess what happens next. When Location 19 reaches 255, it gets set to 0 during the next VBLANK and Location 18 gets increased by 1. Finally, when Location 18 reaches 255, everything gets reset to 0 and the whole thing starts all over again. So, to put things in a more understandable perspective, Location 20 increases by 1 every 1/60 of a second, location 19 every 4.27 seconds (256/60), and location 18 every 18.2 minutes (4.27 seconds*256).
The following routine will tell you the number of jiffies, seconds and minutes that the clock has been running, i.e., since you turned on the computer or last POKEd 18 to 20 with zeros.
40 PRINT "RTCLOCK reads ";J;
" jiffies, or ";S;" seconds,
or ";M;" minutes."
All three locations are set to zero when you turn on the computer or press SYSTEM RESET You can set them to whatever values you want just by POKEing them. Possible uses for RTCLOK include timing things that need precise timing. You can even use it to keep track of the time (what an absurd use for a clock).
This is a temporary register used to store the disk buffer address. It exists so that the OS can use indirect addressing to access the disk buffer. If this doesn't make sense, the BUFADR is not the place for you.
Another hard-core location. ICCOMT holds the CIO (Central Input Output) command and is used as an index into the command table to find the offset for the correct vector to the desired handler routine. Like I said, for hard-cores only.
This is used as a vector to the FMS (File Management System). It is called JMPTBI. by DOS (which doesn't know any better).
Another location used by DOS. DOS calls it BUFADR, but we'll continue to call it DSKUTL so as not to get confused with the OS BUFADR (21,22). DSKUTL points to a buffer that the disk utilities package (DUP) uses when copying or duplicating a file. If the user says it's okay to use the program area while copying or duplicating, then DSKUTL gets the value in MEMLO (743,744). If the user says no way to the program area, then DSKUTL gets the address of DBUF, a special 250-byte buffer at Location 7668.
If you're not a big fan of machine language I/O, then skip this one. PTIMOT is the printer timeout value. It's set by your printer handler software, and initialized by the OS to 30, which represents 32 seconds. If you're good at math you'll realize that 60 would represent 64 seconds. It's updated after each printer status request, getting the specific timeout status from DVSTAT+ 2(748).
A timeout is essentially what it sounds like. The printer (it could also be a disk drive or similar device) says, "Hey, time-out," and takes five. This has the noticeable effect of the printer just sitting there for a brief period of time doing nothing. Then it decides to come back and get to work again. What are you going to do, fire it? Anyway, those of you with the original OS may be very familiar with this situation, since that version of the OS contained a bug causing unnecessary timeouts. You would be doing something like printing when all of a sudden the computer would stop everything for up to five minutes. Version B did away with it.
PBPNT is an index (pointer) into the print buffer. It tells the OS how full or empty the buffer is, and can therefore have any value from zero up to the size of the print buffer, PBUFSZ (30).
PBUFSZ is the size of the print buffer, but not necessarily the size of the print line. The normal buffer size is 40 bytes (which is obviously not the normal line size for most printers). It is initialized to zero by the OS (and not set until P: is opened), and set to four in the case of a printer status request.
Characters get stored in the print buffer on their way to the printer. The OS checks PBPNT (29) to see whether it's equal to the buffer size (which would mean that the buffer is full) and, if it is, the buffer gets sent to the printer. If the buffer gets an EOL (End Of Line) character, then the OS fills the rest of the buffer with spaces and sends it to the printer.
This is used by the printer handler to temporarily hold the character being sent to the printer while it goes off and does some chores.
Zero Page Input/Output Control Block (ZIOCB)The 16 locations from 32 to 47 are used by CIO to make I/O as efficient as possible (remember the speed advantage of page zero). They are set up in the same way as the regular IOCBs (832 to 959) and essentially act as a mirror for the IOCB that wants to be used. In other words, when a CIO operation gets going, the information in the IOCB that's involved is moved to here, where it is used by the CIO routines. When the CIO is all done, then the updated information is moved back to the IOCB. Remember, as complicated as this sounds, it's only done for the sake of speed.
This serves as an index into the handler address table for the file that's currently open on this particular IOCB. If there is no such open file (i.e., the IOCB is free), then ICHIDZ gets set to 255.
The device or drive number. DOS uses it to tell the maximum number of devices, and therefore calls it MAXDEV (I'll bet you can see a connection there). It gets initialized to one.
This is the command byte, which is set by the user, in the course of setting up the regular IOCB, to tell CIO what kind of operation is to be performed (GET, PUT, FORMAT, etc.). It also determines the format of the rest of the IOCB (which will be different for different commands).
ICSTAZ is the status of the last IOCB action taken. The device in question tells CIO what happened, CIO tells the OS, and the OS sets ICSTAZ (a little chain of command here). Hopefully everything went okay, but if it didn't, ICSTAZ is the guy who'll know.
Another buffer address, this one for data transfer. The OS also uses the ICBAZ twins to get the device name from the user (in this case ICBALZ/HZ holds the address of the location where the device name has been stored).
Each device has its own routine to "put" a byte into the device. The OS sets this location to hold the address (minus one) of the routine for the device being used. When the file is CLOSEd (and on powerup), it is set to the address of CIO's error routine for an illegal put (because you can't put something into a device unless it's open).
More buffer stuff. This time we have a counter that is initially set to the maximum number of bytes to PUT or GET in an I/O operation. It gets decremented every time a byte is put or gotten.
Machine language programmers can set this location to the size of the memory block they want to transfer. By checking after each PUT/GET to see if it's equal to zero, you'll be able to tell when the transfer is done.
This is the first byte in the OPEN command after the IOCB number. It tells whether the user wants to READ, WRITE, or both.
Okay, the last location was the first byte after the IOCB number, so guess which one this is? Hey, you're on the ball! ICAXIZ has no specific function, it really depends on the device you're using. CIO pretty much uses it as a working variable, although some serial port functions also use it.
Locations 44 to 47 are also called ICSPRZ or ENTVEC and are spare bytes for local CIO usage.
BASIC's NOTE and POINT commands use these locations to transfer disk sector numbers.
ICAX3Z/4Z give the sector, ICAXIZ gives the byte within the sector. It is also used to store the IOCB number times 16 (since each IOCB is 16 bytes long, this gives an index to the beginning of the IOCB). In this case, it is called ICIDNO.
Sometimes this doesn't do anything. But sometimes (only sometimes) it is called CIOCHR and used to temporarily store the byte that's getting ready to be PUT somewhere (aren't computers wonderful?).
Examples of using IOCBs from BASIC
(ICAXIZ and ICAXIZ are referred to as AUXI and AUX2 respectively).
BASIC Operating System IOCB
#1,12,0; "E:" IOCB =1
Command= 3 (OPEN)
AUXI =12 (READ
AUX2 = 0
Buffer Address= ADR
AUXI =12 (READ
AUX2 = 0
Buffer Address= ADR
GET #1,X IOCB=1
Buffer length = 0
The gotten character is
stored in the accumulator.
Buffer length = 0
The gotten character is
stored in the accumulator.
PUT IOCB =1
#1,X Command= 11 (Put\
Buffer length = 0
The character is output
through the accumulator.
Buffer length = 0
The character is output
through the accumulator.
#1,A$ IOCB =1
Command= 5 (Getrecord)
Buffer length = Len (A$) -1
(no more than 255)
Buffer address= Input line
Buffer length = Len (A$) -1
(no more than 255)
Buffer address= Input line
#1,A$ IOCB =1
BASIC uses a special put
byte vector in the CB to
talk directly to the handler.
byte vector in the CB to
talk directly to the handler.
"S:" IOCB = 6
Command= 18 ("fill")
AUX2 = 0
AUX2 = 0
A couple of uses for this guy. First, and probably most important (after all, it got its name for this one), it is used to hold the status of the SIO (Serial Input/Output) routine currently taking place. Figure 4 lists known values:
FIGURE 4. Status Chart
STATUS also uses TSTAT (793) as a temporary storage location. The other use, you may recall, is as a storage register during SIO routines for the BREAK abort, timeout and error values.
SIO's data frame checksum. A (much) simplified explanation of checksum is called for here. A checksum is essentially a sum of values used to check that the values were received correctly. When data gets somewhere, the computer adds all the values sent into one byte, and then sends that byte as the checksum value. When data is being received, the values are again added and the result compared to the checksum. If the two aren't equal, that means that at least one of the bytes received was incorrect, and the computer usually responds with an error message. In case you're wondering how you can add a whole bunch of bytes together and store the result in just one byte, you can't. If the checksum exceeds 255, then the carry is just added onto it. For example, in . the world of checksums, 254 + 31 = 2,128 + 128 = 1, and so on.
A "checksum sent" flag is located at CHKSNT (59). CHKSUM relies on BUFRFL (56) to tell when the checksum is to be sent or received.
Hey, it's another data buffer! This one is used to hold the stuff that gets sent out or received during 110. Actually, BUFRLO/HI is a dynamic pointer into the buffer (which just means that it points to the next byte to be sent/ received rather than always pointing to the beginning of the buffer).
SIO and DCB (Device Control Block) both use this pointer.
A pointer to the byte right after the end of the data buffer described in the previous location. This helps SIO and the DCB determine when the buffer is full.
Sometimes you may get an error message trying to do stuff like reading from or formatting the disk. Before you tell the user to go toss the disk in the trash, however, you'll probably want to doublecheck to make sure that there really is something wrong with the disk, and it wasn't just a temporary boo-boo. CRETRY specifies how many times to try again before giving up. It is initialized to 13.
The same basic idea as CRETRY, but where CRETRY double-checks that a specific command doesn't work, DRETRY double-checks to make sure that . the whole device doesn't work. It is initialized to one.
If BUFRFL equals 255, then the date buffer is full. If it doesn't, it isn't.
If RECVDN equals 255, then all the data that was supposed to be received has been. If it doesn't, it hasn't.
If XMTDON equals 255, then all the data that was meant to be sent was. If it doesn't, it wasn't.
If CHKSNT equals 255 (you should know this already), then the checksum was sent.
More checksum stuff. A zero here means that a checksum follows the current transmission. No zero means no checksum.
By now you should be getting the idea that buffers are pretty popular items around a computer. Here's another buffer to further enforce that idea. This time we have one for cassette data. Like BUFRLO/HI, BPTR is actually a pointer into the buffer (which is located at CASBUF [1021 to 1151]), indicating how full or empty the buffer is. It can be anything from zero to the value in BLIM (650). If it's equal to BLIM, then the buffer is either empty or full (depending on whether it was being read into or written out of, respectively). It is initialized to 128.
You load in a program from cassette and while it's loading, the computer goes "beeeep (pause) beeeep (pause) etc.," right? Well, the pause has a name. It's called an "inter record gap:" Can you say "inter record gap"? Sure, I knew you could. Anyway (so much for the comic relief), FTYPE specifies the kind of gap to put on the tape. It equals 0 for normal gaps (like in a CLOAD tape), 128 for continuous (long) gaps (like in an ENTER "C:" tape).
FTYPE gets its value from ICAX2Z (43), which gets it from DAUX2 (779), which gets it from the user.
Okay, we're still loading from cassette. How do we know when there's no more to read? The last record (each beep when loading represents a record) on a cassette file has a command byte of 254 and is called the EOF (End Of File) record. FEOF is set to 255 when the EOF record is reached, and 0 before that.
See CASBUF (1021) for an explanation of the way cassette records are structured.
Quite simply, the number of beeps that the Atari makes when you OPEN the cassette handler: one beep for read, two for write (type "CLOAD" and press RETURN for a demonstration).
SOUNDR is used to turn the beeping off (or back on) while the cassette or disk program is loading. A zero here will stop the beeping, anything else will get it going again. Also see location PACTL (54018). The beeping is caused by the loading of data from the right channel. Atari added this to the computer so that its educational tapes can talk to you while loading programs. Ah, hah! This must mean that the left channel still can be heard even if you change the value in location 65.
CRITIC is used to tell the OS that the current 110 operation is time-critical (disk or cassette operations, for example). This is important, because in the case of time-critical I/O it is important that the computer spend as little time in vertical blank as possible. When CRITIC is a nonzero value, the OS knows not to execute the second stage of the VBLANK process (CRITIC is checked at the end of Stage 1). Since there are some things happening during Stage 2 that you may not want to interrupt (check the OS listing if this is really of concern to you), CRITIC should be used only when necessary. To experiment, poke a 2 into 66 and then press any letter. The repeat capability will not work and CONTROL-2 will sound funny. You can't press any key twice in a row.
The following seven bytes are called FMSZPG and serve as zero-page registers for the disk-file manager system (FMS).
When the FMS does disk I/O, it needs to know the user filename so it can OPEN the file. It expects to find it in a buffer pointed to by ZBUFP.
Zero-page drive pointer. FMS also uses ZRDVA in its setup, free sector and get sector routines. I know this sounds somewhat cryptic, but it's that kind of location.
A pointer to the sector buffer.
If things go wrong during disk I/O, this is where you can find the error number. FMS initializes it to 159.
If the START button is held down when the Atari is first turned on, CKEY is set to one (zero otherwise). This indicates that a cassette file is to be booted.
If a cassette file is booted and the boot is successful, CASSBT gets set to one. Zero means boot no goot. Also see BOOT? (9).
A location of all trades, DSTAT is used mainly by the display handler to indicate display status and as a keyboard register. It is also used to indicate a cursor out of range error, the BREAK abort status, and too little memory for the desired screen mode.
Try leaving the Atari on for about nine minutes without pressing any keys (or save yourself some time by POKEing ATRACT with 128). You've probably run across this effect before. It's called the "attract mode" and, as you can see, causes the colors on the screen to change every four seconds or so, at subdued brightnesses. Why, you may ask? If you leave your computer alone for several hours with a picture on the screen that doesn't change (like when you break for lunch and forget to turn the TV off), it can "burn" the picture tube of your television set and leave a permanent, although faint, image on the screen. You obviously don't want this to happen, so Atari thoughtfully created this solution.
Whenever you press a key IRQ (Interrupt ReQuest) sets ATRACT to 0. Otherwise, every four seconds VBLANK increments it by 1. When it reaches 127 it gets set to 254, and the Atari enters the attract mode. That's the way it stays until a key is pressed.
The attract mode only changes the four color registers COLPF0 to COLPF3 (53270 to 53273) and the background COLBAK (53274). That means that you'll have to write your own atract routine for DLI-induced colors.
If you're using joysticks but not the keyboard, you'll have to set ATRACT to zero every few minutes within your program.
This is one of the two locations used to change the colors in the attract mode (COLRSH is the other). DRKMSK makes sure that the colors aren't too bright. It's normally set to 246 during the attract mode.
For the curious machine-language programmers, DRKMSK is ANDed with the original color to mask out part of the brightness nibble. This is done during stage two VBLANK.
The other location for changing colors, COLRSH actually does change the colors. It contains the current value of RTCLOK + 1 (19).
Machine-language programmers, COLRSH gets EORed with the color registers (and background) before DRKMSK does its stuff.
Locations 80 to 122 are used by the screen editor and the display handler.
Guess what "TEMP" stands for? That's right, this is a TEMPorary (get it?) register used to move data to and from the screen. TEMP gets used by the display handler, which also calls it TMPCHR.
Another temporary register for the display handler, this time used to hold the number of entries in the display list.
Another tough name to figure out. If you're using graphics mode zero (or have a text window in the mode you're using), LMARGN determines the left margin for text. It's initialized to 2, but you can set it to whatever you want (up to 38). Try POKEing various values into this location.
The right margin (I'll bet that somehow you'd figure that out already). It's initialized to 38, and you can also set it to whatever you want (try and set it higher than the left margin though, and less than 40, okay?).
A few words about margins. SYSTEM RESET will restore them to their initial values. Text that is already on the screen will not be affected when you change the margins. Finally, logical lines (the longest a BASIC line can be) couldn't care less when you put the margins. Three lines on the screen and that's it for your logical line, baby, whether that means 120 characters or three.
This tells you the row on the screen that the cursor is currently on. It works in all the GRAPHICS modes and therefore has a range of 0 to 191 depending on the mode being used. Don't forget that a row is a horizontal line, not a vertical one (you'd be surprised at some of the people that forget). Rows are numbered from top to bottom, 0 being the top.
The column that the cursor is on, ranging from 0 to 319. Location 86 can only get set to 1 in graphics mode 8 (where the column number can exceed 255). Columns are numbered from left to right, 0 being the leftmost column. Incidentally, ROWCRS and COLCRS define the next cursor position to be read or written to, not the last one.
This location tells the OS what graphics mode is currently being used (so it knows how to respond to a PLOT or some other screen I/O command). When you OPEN the screen (which the GRAPHICS command takes care of for you), the value of the AUXI byte is stored in DINDEX. This means that DINDEX can have a meaningful value of anything from 0 to 11, keeping in mind the GTIA modes are numbered 9 through 11.
Most of the time you'll just leave DINDEX alone, because BASIC takes care of it for you. The times that it does come in handy, however, is when you want to use mixed mode display lists. It also comes in handy when you want to use the so-called "GRAPHICS 75," which gives you twice the resolution of graphics mode 7 with the same number of colors (machine-language programmers also know this mode as ANTIC mode "E"). The problem with using this mode is that it is, obviously, halfway between graphics modes 7 and 8. That means that the display list is structured the same as a graphics mode 8 display list, but you have to PLOT to it like it was graphics mode 7. So what, you say? Let's look at an example? The following routine sets up what is called a GRAPHICS 75 screen by changing a GRAPHICS 8 display list:
100 GRAPHICS 24
120 POKE DLIST+3,78
130 FOR LINE=DLIST+6 TO DLIS
150 IF TYPE=15 OR TYPE=79 TH
160 POKE LINE,TYPE
170 NEXT LINE
180 COLOR 3
190 PLOT 0,0:DRAWTO 79,85
200 POKE 89,PEEK(89)+15
210 PLOT 80,0:DRAWTO 159,95
999 GOTO 999
A brief explanation of what's going on here. We first set up for a graphics mode 8 screen with no text window. Then we find out where the display list is (see SDLSTL [560,561]) and then change each of the graphics mode 8 commands in it to graphics mode 75s. Then, since we have no text window, we must go into a continuous loop or else the screen will switch back to graphics mode 0 (take out line 1000 and see for yourself). RUN the program and you will see the screen go from blue to black as the display list changes. You now have a screen that is 160 dots wide and 192 dots high. Try adding the following lines to the preceding routine:
180 COLOR 3
190 PLOT 0,0:DRAWTO 159,191
Now RUN the whole thing. Uhh, oh! What happened? It's supposed to draw a blue line from the top left corner of the screen all the way down to the bottom right corner. Well, unfortunately the OS still thinks that it's in graphics mode 8, and in graphics mode 8 things get plotted differently than we want here. Let's trick the OS into thinking it's in graphics mode 7. That way it'll plot properly (technically speaking, we want two bits to represent a pixel rather than one). Add the following line:
175 POKE 87,7
RUN it again and whoops! ERROR 141? That means that the cursor went out of its allowed range. We forgot that graphics mode 7 only allows 96 rows. Change Line 190 to the following:
190 PLOT 0,0:DRAWTO 79,95
Now we're okay, but how do we draw in the lower half of the screen? Unfortunately, the tables that tell the OS how many rows and columns each mode has are in ROM, so we can't fool the OS into thinking that there are more rows. The only way around this problem is to treat a GRAPHICS 7.5 screen as being two separate screens, a top and a bottom (machine-language programmers can also write their own plot and draw routines). You can use SAVMSC (88,89) to pick the screen you want to use. Try the following program additions and then look at SAVMSC to see what's going on:
200 POKE 89,PEEK(89)+15
210 PLOT 80,0:DRAWTO 159,95
(This is a tedious process but it's the price you have to pay if you want the benefits of GRAPHICS 7.5)
This is the location of the place in memory where the data is kept that goes onto the screen. Each number in memory represents one character on your TV or several pixels if in a graphics mode. The value at memory location SAVMSC goes at the upper left-hand corner of the screen. The next memory location then goes left side, one row down.
When you do I/O to the screen, the OS uses this address to figure out where to PLOT and PRINT. So, for example, the following line will put the letter "A"; in the upper left-hand corner of your graphics zero (or one or two) screen.
But wait, you say. CHR$(33) doesn't give us an "A"; what's going on here? I'll tell you. The Atari stores the characters in memory in a different order than the ATASCII order (which is what CHR$ uses). See CHRORG (57344) to find out how to convert from one to the other. Anyway, the values in screen memory represent the internal character order rather than the ATASCII one.
If you're not using a text mode, the values you poke to the screen will, obviously, affect the pixels on the screen (the dots on the screen). A pixel is represented by one, two or four bits. See location DMASK (672) to find out what bits in a byte affect which pixels in each mode (that was easy for me to say). Then try POKEing around. You may want to check CHRORG again; it has an example of using such POKEs to get characters on the screen in graphics mode 8.
Okay, so now you know how to change the first character on the screen. What if you want to change the sixth character on the tenth row; how do we know how to find it? Figure 5 shows how many bytes per row are required for each graphics mode.
Now, if you want to change character X in row Y, just multiply Y by the number of bytes per row for the mode you're using and add X (don't forget that the first row and column are numbered zero, not one). Add this value to the address in SAVMSC, and POKE away. For example, let's put the letter "B" in the middle of a graphics zero screen (row 11, column 19):
100 GRAPHICS 0
130 POKE SCREEN+POS,34
We want to make sure that we don't try and change a byte that isn't part of the screen, so let's add another line to our chart, this one giving the number of rows in each mode. We'll also multiply the number of rows times the bytes per row to get the total number of bytes taken up by the screen memory (Figure 6).
Now these values, when added to the address in SAVMSC, will give you the value of the first byte after the end of screen memory. What they don't tell you is how much memory the whole graphics mode takes up. Why not? Because they don't take into account the display list (see SDLSTL [560,561]) and a few bytes that get trapped in the middle of everything. So how do we get this total memory amount? Well, it turns out that RAMTOP (106) points to the top of free memory, which coincides with the first byte after the end of screen memory. MEMTOP (741,742) points to the top of BASIC memory, which coincides with the first byte before the display list. So, if we subtract MEMTOP + 1 from RAMTOP * 256 (RAMTOP is in terms of pages), we'll get the total memory required. I'll save you the trouble and just give you the values. Our final chart is Figure 7.
You may have told yourself by now that you can change the values in SAVMSC and thereby change where the screen is. And if you can change where the screen is, you can keep more than one screen in memory at the same time. Well, you're half right. You definitely can have more than one screen in memory at the same time, but unfortunately SAVMSC only tells the OS where to PRINT and PLOT (and the like) to. It doesn't tell the computer what to display on the television screen. Fortunately, there is another pair of locations that tell what to display, and the word "display" should tip you off to where they are; they're in the display list (this is kind of like adult Sesame Street, isn't it?). Specifically, they're the fifth and sixth bytes in a normal (unaltered by you) display list. Try the following:
130 IF LOW=256 THEN LOW=0:PO
140 POKE DLIST+4,LOW
150 FOR DELAY=1 TO 10:NEXT D
160 GOTO 120
This will move the starting address of the screen one byte forward at a time, having the effect of swallowing up whatever was on the screen when you ran it. Press SYSTEM RESET to stop it and get everything back to normal.
A few things to note here. First, if you let this run for a while (get rid of Line 150 to make it hapen faster), the screen will suddenly fill up with a whole bunch of garbage. This "garbage" is actually your BASIC cartridge! The starting screen address has been moved so far forward that it has now entered the BASIC zone. You may have astutely noted that the garbage didn't scroll onto screen smoothly, but rather just sort of suddenly appeared. This is because the screen memory has committed a no-no. It has crossed a 4K boundary. What is a 4K boundary? It's the boundary between one group of 4096 bytes and the next one. How do you tell where one is? Well, first of all, the address of a 4K boundary is a multiple of 4096. Better yet, if you're working in hexadecimal, the leftmost digit in the fourdigit hex number is the "4K digit" (this is not an official term). When it gets changed, a 4K boundary has been crossed. Okay? In any case, the whole purpose of this explanation was simply to tell you that the screen memory is not allowed to cross over a 4K boundary. The GRAPHICS command usually takes care of this for you, but if you're setting up more than one screen, you'll have to be careful.
Going way back to our program example, you should also note that despite what's happening on the TV set, the OS still thinks that the screen is where it was originally, since we haven't changed SAVMSC. If you expect the OS to keep up with you, change SAVMSC as well as the display list.
Finally (and you thought it would never end), before we move onward and upward, a few bits of memory trivia. The address of the text window memory can be found at TXTMSC (660,661). And, in case you thought you weren't going to get a good multiple screen example, you're right. Just kidding.
99 REM Get everything set up
100 GRAPHICS 1:PRINT #6;"THI
S IS SCREEN ONE"
140 POKE 106,DLIST1H-4
150 GRAPHICS 2:PRINT #6;"THI
S IS SCREEN TWO"
189 REM Do the flipping
190 POKE 560,DLIST1L:POKE 56
200 POKE 88,SCRMEM1L:POKE 89
210 GOSUB 1000
220 POKE 560,DLIST2L:POKE 56
230 POKE 88,SCRMEM2L:POKE 89
240 GOSUB 1000
250 GOTO 190
999 REM Pause between scree
1000 FOR PAUSE=1 TO 200:NEXT
Sorry, but no explanation for this one. You should ne able to figure it by yourself. I will, however, give you the following lines which you may want to add to make the screen look a little less messy.
205 POKE 559,34
235 POKE 559,34
1005 POKE 559,0