Program control of Atari BASIC, OSS Super Cartridges, the R-Time 8 cartridge and, in a limited fashion, SpartaDOS X, is fairly simple but for one fact: there's nearly no documentation available on the subject. What I present here is gleaned from a bit of disassembly and a lot of experimentation (pretentious word for "try it until it don't crash"). Note that all following references to a cartridge being "present" imply that it is turned on--if it's plugged in but turned off, consider it "absent". You may always consider the RT8 to be absent unless you're actually trying to access it. First let's look at the addresses used for, or in conjunction with, auxillary ROM/RAM control.
PORTB has already been covered; just keep in mind the function of the BASIC bit. BASICF is a flag in low memory to tell the OS, on system reset, how to set bit 1 of PORTB. If this flag contains any non-zero value the BASIC ROM will be disabled.
TRIG3 is an address on the GTIA chip which was used for joystick trigger #3 on the 400/800. On the XL/XE it is a cartridge status indicator; if a cartridge is present it reads 1, otherwise it will be 0. There is no other possible reading at this address. GINTLK is set, on boot, by the OS and is a copy of TRIG3. The OS compares GINTLK with TRIG3 during the deferred vertical blank interrupt and, if the two don't match, goes into a "soft" lockup (i.e. a reset will re-boot).
CARTCK holds a checksum, calculated on boot, by the OS. On a reset, if a cartridge is present as signalled by TRIG3, the OS re-calculates the sum and compares it with CARTCK. If the two don't match, the OS assumes you've pulled or inserted a cartridge and immediately re-boots. Note that Mapping the Atari is wrong on this: it applies to all XL/XE's, not just the 1200.
CARTCK, TRIG3 and GINTLK are effective for all cartridges except (in part) the RT8. One other important thing to note is that the TRIG3/GINTLK comparison occurs during the deferred vertical blank. This means you can fool around with a cartridge to your heart's content as long as the stage two vblank doesn't occur and you don't hit Reset. You can prohibit vblank2 in any of three ways: disable all NMI's, set CRITIC to a non-zero value or, most simply, use a SEI opcode.
The hardware address range for all cartridge control is $D500-$D5FF. Within that page, OSS cartridges use $D500-$D50F, the CSS MUX OS uses $D570-$D57F, the RT8 uses $D5B8-$D5B9, and SDX uses $D5E0-$D5EF. This sounds straightforward; unfortunately it isn't.
I made no mention of Atari cartridges in the address ranges because once you stick one of the beasts into the slot, your only control over it comes with the power switch or by using SDX. An Atari cartridge can not be turned off by software unless SDX is present (even if turned off). SDX can control one because it sits between the computer and the cartridge and can, thereby, zap it electronically. However, the foregoing discussion of TRIG3 and GINTLK remains fully applicable.
Though the control range is $D500 to $D50F, the cartridge address decode logic is only four bits wide. This means that any access (read from, write to, or otherwise manipulate) a $D5xy address affects the cartridge which ignores the "x". OSS cartridges react to the whole $D500 page based on the low 4 bits of the address.
To enable an OSS cartridge bank, add the bank number to $D500 and access that address (i.e. for bank n, STA $D50n, LDA $D50n, STA $D500,X where the x register holds n, etc.) In theory, a cartridge should be able to contain up to fifteen 8k banks and still allow you to turn it off. In practice, they contain two or three.
For OSS cartridges, the ROM bank number is found at location $AFFF. Valid values at $AFFF are 0, 3 and 4 for Action! and 0, 1 and 9 for MAC/65. Other bank values produce varying results. MAC/65 ignores bits 1 and 2 so any value from 0 to 7 results in selection of either the odd or even bank. With Action! attempts to select other bank numbers result in selection of one of the real ones or in selection of nothing i.e. a monitor shows a pile of $AF's in the $AF page just as when you examine page $D7 and get $D7 at all addresses. BASIC XE has banks 0, 1 and 9 but bank 9 is RAM. In bank 9, the BXE cartridge is off but TRIG3 stays high; a sneaky way to avoid having to worry about GINTLK while using the RAM under the cartridge. There are two constants for cartridges: Addressing bit 3 alone turns cartridge ROM off and, bank 0 is the bank in which the cartridge boots and initializes. Here are "maps" of the banks in two cartridges:
; MAC/65 0123456789ABCDEF: Bank Selection rrrrrrrr r r r r: r=rom, empty=ram 01010101 9 9 9 9: bank # 01 9 : valid rom banks
Action! 0123456789ABCDEF: Bank Selection rrrrrr r : r=rom, empty=ram 000340 3 : bank # 0 34 : valid rom banks
Following are several examples of cartridge and BASIC control with SDX not present. I'll start with equates for all examples from Mapping the Atari (XL edition):
WARMST = $08 BOOT? = $09 CRITIC = $42 RAMTOP = $6A COLDST = $0244 CARTCK = $03E8 BASICF = $03F8 GINTLK = $03FA TRIG3 = $D013 PORTB = $D301 NMIEN = $D40E EDITRV = $E400
Here's the simplest: turn off a cartridge and enable BASIC assuming both are actually present.
SEI Kill stage 2 vblank STA $D508 Kill any cartridge LDA PORTB AND #$FD Drop basic bit STA PORTB LDA TRIG3 This should be 0 STA BASICF Flag it and STA GINTLK correct the cart shadow CLI Enable stage 2 vblank
Now let's access RAM under a cartridge:
SEI Kill stage 2 vblank LDA PORTB PHA Save Portb ORA #$02 Kill basic rom STA PORTB LDA #$08 Assume no cartridge. LDX TRIG3 Check assumption. BEQ GOTBNK Go if none or off, LDA $AFFF else get the bank GOTBNK PHA Save cartridge bank. STA $D508 Kill any OSS cartridge LDA TRIG3 Set the shadow before STA GINTLK the stage 2 vblank! CLI Enable stage 2 vblank
Do whatever in the RAM, then restore the previous status:
SEI Kill stage 2 vblank PLA Recover cartridge bank TAX and restore the cart STA $D500,X to it's prior status LDA TRIG3 Reset Gintlk to STA GINTLK correct status PLA Restore prior Basic STA PORTB rom status CLI Enable stage 2 vblank Just turning a cartridge off is simple: SEI Kill vblank 2 STA $D508 LDA TRIG3 Make sure it went STA GINTLK off and flag it CLI Enable stage 2 vblank
Cold-starting an OSS cartridge is only slightly more complex:
SEI Kill vblank 2 STA $D500 Enable cart bank 0 LDA TRIG3 Set the shadow STA GINTLK correctly LDA PORTB PHA ORA #$01 STA PORTB Ensure OS is on CLC Calculate the LDX #0 checksum TXA for reset. CSLOOP ADC $BFF0,X Note the sum INX includes the BNE CSLOOP first 240 bytes STA CARTCK of the OS ROM. PLA Restore any RAM STA PORTB OS (or Sparta) CLI Enable stage 2
After cold-starting an OSS cartridge or BASIC, set WARMST to 0 to flag a boot so that the buffer pointers are cleared. If you don't, you can, for example enter BASIC, type LIST and get an endless display of zeros and/or a lockup. Then initialize the ROM. With Action! and BASIC this doesn't matter as the initialization routines just RTS; with BASIC XE I'm not sure; with MAC/65 it's required. To initialize any cartridge:
LDX #$FF Say we're on a boot STX COLDST and make sure all INX flags reflect this STX WARMST INX STX BOOT? JSR INIT Go do it LDX #$FF Say we're back to STX WARMST normal status, i.e. INX what happens on Reset. STX COLDST Note that some or all INX carts play with some STX BOOT? of these flags! RTS INIT JMP ($BFFE) Cartridge init vector
After enabling or disabling a cartridge or BASIC, you also have to ensure top of RAM and screen pointers are correct. To do this, execute a "GRAPHICS 0". In machine language terms, you set RAMTOP and then close and re-open channel 0 to the "E:" device. You can do this in the traditional manner via CIOV or more simply by calling the following subroutine with the accumulator holding $C0 if turning ROM off and $A0 if turning it on.
GRAPH0 STA RAMTOP Either $A0 or $C0 LDX #0 Indicate channel 0 LDY #2 Point to Close vector JSR EDO LDY #0 and now to Open vector EDO LDA EDITRV+1,Y PHA LDA EDITRV,Y PHA RTS
Be aware that turning off BASIC XE does not free up the RAM under the cartridge if you intend to later restore the cartridge. BXE uses that RAM as well as that under the OS floating point routines and also (undocumented) sets an interrupt vector in the last page of RAM ($FFFx). One final note on turning ROM off or on: following the Graphics 0, an RTS under Sparta will usually lock up the keyboard requiring a reset. Sparta installs its own E: handler so when you use the OS handler to reopen E: Sparta's vectors are no longer valid. The simple way around this is to exit via a JMP (DOSVEC). This is probably a good idea with any command-processor FMS where you can use a batch file instead of an autorun to set things up.
SDX boots in bank 0 but normally works in bank 1 with one subroutine call back to bank 0 via low RAM which I suspect is used to load files from CAR: The cartridge contains eight different ROM banks (0 to 7), but I have not discovered any single location containing a bank identifier and I doubt there is one as its existence essentially would mean a "hole" in the middle of each ROMdisk bank. The control address for the X cartridge is $D5E0 used similarly to $D500 with an OSS cartridge.
The following code will leave the currently selected bank in the Y register with version 4.20. With other versions, you're on your own.
LDA $A004 LDY #7 LOOP CMP XBANK,Y BEQ GOTIT DEY BPL LOOP LDY #$01 Can't figure, make it 1 GOTIT RTS XBANK .BYTE $32,$1F,$02,$1D .BYTE $D4,$1C,$61,$56
The ROMdisk directory is at the beginning of bank 2 and follows normal Sparta format except that the address of the first sector map is the actual starting location in ROM of the stored program. The first two entries look like this:
.BYTE $08 Status: In use .WORD 16384 Start: Bank 2 Offset 0 .WORD 598 Length .BYTE 0 Length (high byte) .BYTE "MAIN " .BYTE 1,1,70 Date .BYTE 251,0,0 Time ; .BYTE $08 Status: In use .WORD 16982 Start: Bank 2 Offset 598 .WORD 7288 Length .BYTE 0 Length (high byte) .BYTE "SPARTA SYS" .BYTE 6,2,89 Date .BYTE 15,28,40 Time ;
To convert the starting address to a bank and offset within the bank: bank = int(address/$2000) and offset = address-$2000*bank
CAR.COM uses the following combinations to control all three ROMs in the cartridge area. The values under "SDX" and "OSS" are offsets from $D5E0 and $D500 respectively and those under BAS are the value in bit 1 of PORTB. The "on" under the OSS column is the value found at $AFFF before the cartridge was last turned off and used to reenable it. The $0C value for SDX is what causes it to latch any cartridge off and the $08 makes it transparent so that the cartridge ROM is accessible. SDX OSS BAS OPERATING CONDITION $01 $08 1 in DOS or low RAM $0C $08 1 in high RAM (X.COM) $0C $08 0 in BASIC $08 on 1 in cartridge If you're going to play with SDX banks remember that any read or write to a $D5Ex address will affect an OSS cartridge and TRIG3. Since TRIG3 is affected, GINTLK and CARTCK also come into play. So the example given of how to access RAM under a cartridge needs to be modified if SDX is present. Let's look at a subroutine to access RAM in the cartridge space taking in the possibility of the presence of BASIC, SDX, or an OSS cartridge. ROMCTL LDA PORTB PHA ORA #$02 Any Basic rom off STA PORTB LDA RAMTOP This might be easy CMP #$A0+1 BCC NOLUCK Not quite. PLA See note a. following STA BASICF for an explanation JMP DOSTUFF NOLUCK LDA TRIG3 Is a cart present? BNE CART Yes, go. JSR DOSTUFF Still fairly easy PLA STA PORTB RTS CART PLA See note a. following STA BASICF for an explanation. ; SEI Kill stage 2 vblank LDA $AFFF Get OSS bank number PHA Save it STA $D5E8 Turn off both carts JSR DOSTUFF We know a cartridge was on. Now we have to restore it correctly. PLA Recover bank number CMP #$10 Valid for OSS? (note c.) BCS SDX No, must be SDX TAY STA $D500,Y Restore OSS cart bank BCC CARXIT Go always SDX STA $D5E1 Enable SDX normal bank STA $D508 Kill OSS cart (note b.) CARXIT CLI RTS a. The reason for discarding the PORTB entry value is to allow for 512k+ RAM expansions. As mentionned previously, the OS doesn't know extra RAM exists and has no way of knowing BASIC does not exist on large upgrades. As a result, it sets PORTB and BASICF based solely on the Option key at boot and uses BASICF to determine which status to restore on a reset. On large RAM upgrades this leads to major problems for programs using extra RAM as the program can end up in the wrong 256k bank. Unless BASIC is actually on, it is always advisable to flag it off and to set its bit high in PORTB. b. We knew a cartridge was on or we never would have got to that portion of the code. As it wasn't the OSS cartridge, it had to be SDX. But, because the dumb OSS cartridge reacts to the $D5E1 address, we had to turn it off again after enabling SDX. For the same reason, a single access of $D5E8 was sufficient to turn both off. c. The comparison of the bank number to 16 to determine its validity as an OSS bank number is that used by ICD in the code for the RT8 handler. The test is, I believe, made on the assumption that the X cartridge is in bank 1 where the value at $AFFF is 87 for version 4.20. There are two banks where values less than 16 are found at $AFFF, namely 0 (value 7) and 4 (value 3). The R-Time 8 Control of the RT8 is built into all versions of Sparta from 3.2 on. As far as I know, all you can do with the RT8 is set or get time and date information. The only problem in doing this is that accessing the RT8 registers will affect an OSS cartridge. Because of this the RT8 has two identical user accessible registers $D5B8 and $D5B9. According to the RT8 source, addressing $D5B8 will turn off a cartridge and $D5B9 will turn one on. The RT8 has seven internal registers which work in binary coded decimal. Starting from #0 they are: seconds, minutes, hour, day of month, month, year, and day of the week (#6). Seconds and minutes range from 0-59, hours from 0-23, day from 1-31, month from 1-12, year from 0-99. Day of the week ranges from 0 (Saturday) to 6 (Friday). When you read or write one of these registers the sequence is always the same: 1. Wait until the RT8 is not busy. 2. Store a value from 0 to 6 into $D5B8 or $D5B9 indicating the register you wish to address. 3. Read/write the same address to get/set the most signifigant digit (the low four bits are the valid data). 4. Read/write the same address to get/set the least signifigant digit (the low four bits are the valid data). The source code released by ICD indicates that reading a register should be repeated up to three times accepting two values that match or, failing a match, the first one. When setting a register, it recommends reading it immediately afterward to ensure the value was really accepted and allowing 10 tries. Here's one way to read and write RT8 registers without worrying about an OSS cartridge. Much of this is from the source released by ICD. In this example, the buffer is set up in the same order as the RT8 registers. With Sparta, you would have to cross refer to the order in which DOS saves time and date and keep a separate byte for day of the week. *= $F0 Floating point zero page TEMP1 *= *+1 TEMP2 *= *+1 RETRY *= *+1 BUFFER *= *+1 Seconds *= *+1 Minutes *= *+1 Hours *= *+1 Day of month *= *+1 Month *= *+1 Year *= *+1 Day of week ; *= WHEREVER SEI Kill vblank2 LDA $AFFF Get any cart bank CMP #$10 Is it valid? BCC SAVBNK Yes go, else use LDA #$08 the "off" value. SAVBNK PHA Save cart bank Verify rt8 present and working JSR READ Get seconds CMP #60 BCS GLITCH if >59 then error STA TEMP1 LDA RTCLOK+2 ADC #90 WAIT CMP RTCLOK+2 Wait about 1.5" BNE WAIT JSR READ Read again CMP TEMP1 Same as last? BEQ GLITCH Yes, not working SEC SBC TEMP1 Ensure BCS CHECK3 It is ADC #60 else did it roll over? CHECK3 CMP #3 BCC RT8OK Yes, rt8 is ok GLITCH LDA # LDX # >RT8ERR EXIT STA ICBAL STX ICBAH LDA #9 Print to eol STA ICCOM STA ICBLH Plenty of length PLA TAX Restore cart bank STA $D500,X CLI restore vblank2 LDX #0 Select channel 0 JMP CIOV Exit with message First read the clock regs into the buffer RT8OK LDX #6 Point to day of week RCLOOP JSR READ DEX BPL RCLOOP Change values you want in the buffer and then write it back to the clock LDX #6 RCLOOP JSR WRITE BNE GLITCH Exit if write failed DEX BPL RCLOOP else do all 7 LDA # LDX # >RT8SET BNE EXIT Branch always RT8ERR .BYTE "RT8 Error",155 RT8ERR .BYTE "RT8 Set",155 ; Subroutine: wait til clock is ; not busy or exit on time out. ; Enter: x=clock reg to access (0-6) ; Exit: x unchanged, clock ready, ; and clock register selected WAITCL LDY #$FF Timeout value WAITC LDA $D5B8 AND #$0F If low nybble=0 BEQ READY clock not busy DEY BNE WAITC Else time out READY STX $D5B8 Set reg #x to read/wrt RTS ; Subroutine: read rt8 reg once ; In x=reg# ; Out a=byte x=reg# READ1 JSR WAITCL LDA $D5B8 Get high byte LDY $D5B8 Get low byte AND #$0F Convert bcd to hex STA TEMP1 ASL A Clears carry ASL A ADC TEMP1 ASL A STA TEMP1 Temp1=(high*10) TYA Add in low byte AND #$0F ADC TEMP1 Return byte in a RTS Note c=0 x=x y=trig3 ; Subroutine: read a clock register ; and accept best 2 of 3 readings ; or the first if none match. ; in: x=reg# ; out: a=value($) READ JSR READ1 STA TEMP1 JSR READ1 CMP TEMP1 BEQ REXIT STA TEMP2 JSR READ1 CMP TEMP2 BEQ REXIT LDA TEMP1 REXIT RTS ; Subroutine: write clock register ; with value stored in buffer offset ; by x. Allow 10 tries. ; in: x=reg# ; out: x=reg#, z flag set if ok WRITE LDA #9 STA RETRY WRT2 LDA BUFFER,X LDY #$FF Convert to bcd SEC SUB10 INY SBC #10 BCS SUB10 ADC #10 PHA low byte TYA PHA high byte JSR WAITCL y=trig3 PLA High byte STA $D5B8 PLA STA $D5B8 Low byte JSR READ Verify it set CMP BUFFER,X correctly BEQ WRTXIT It did! DEC RETRY BPL WRT2 Never 0 if failed WRTXIT RTS The Multiplexer! Operating System (MUX) The MUX OS makes frequent access of registers in the $D57x range. A cursory glance at the ROM reveals it uses the following registers on a read-only basis 1, 6 and 7. Registers 2, 3, B, C and E are accessed as write-only while 0 is read/write. Every one of these addresses will affect an OSS cartridge. I found no indication in the MUX code that it makes any effort to accomodate a cartridge, GINTLK or TRIG3. While I have managed to work around an SDX cartridge and an RT8 in controlling BASIC, OSS cartridges, and even Atari cartridges (with SDX present), I can see no way of doing so with the MUX OS. I believe the idea with the MUX is that once the plug's in the port, you can't use a cartridge anyway. That's kind of unfortunate as it denies you use of a cartridge and access to the built-in monitor. As I don't have a MUX to experiment with, I leave that to someone else. Cold and Warm Starting and Parallel Devices If you turn off an OSS cartridge and then re-boot under most versions of SpartaDOS, you get a "soft" lockup as DOS will enable a cartridge as part of its boot process in testing for BASIC XE. Simply hit Reset and the computer will boot again with the cartridge on. If you want to reenable the SDX cartridge after having turned it off using the COLD command, the method should be obvious by now. What is not obvious are a few other addresses and quirks in warm and cold starting. On boot the OS calculates ROM checksums and compares them to ones stored in the OS itself. If these don't match, boot doesn't happen; you end up staring at the Self Test screen and a red bar under the heading "ROM". This can easily occur on a system with a PD because cold starting the computer does not cold start the PD any more than it does a cartridge. If PD ROM is enabled, as for a modem handler on a BBS, and you attempt to cold start, you will inevitably end up in Self Test because the ROM checksum will fail. It is supposed to include the floating point ROM at $D800, but instead gets a bank of the PD ROM. Finally, if you're just going to warm start, decide whether or not you want to emulate a press of the Reset key. Jumping to the warm start vector at $E474 is not the same as pressing the key; the vector points past the hardware initialization routines. If you want to ensure you clear out all garbage (left over player missiles, keypress, etc.) you have to use the chip reset vector. The following routine has varying results dependant on the entry point. To enable SDX, enter at XCART. To enable an OSS cart alone, enter at CART. To just cold start without touching the cartridges, enter at COLD. To simulate a press of Reset, enter at WARM. XCART SEI Always before $D5xx access STA $D5E0 Enable SDX CART SEI Again, dependant on entry STA $D500 Enable OSS cart COLD DEC COLDST Force a boot WARM SEI Just in case LDX #0 STX NMIEN Ditto STX $D1FF Enable floating point STX $D1E2 Kill MIO RAM (this and DEX following just in case) STX $D1BC turn off BlackBox RAM STX PORTB Ensure ROM OS is on to go JMP ($FFFC) through chip reset vector I won't go any further on this as the Black Box and MIO are, unlike Atari and OSS products, fairly well documented. Whew, when I started this I never thought it would turn into such a monster nor did I think it would take so long to come up with all the ins and outs. Now that you know how easy it is to make use of the RAM under cartridges, under the OS and in extra memory, I look forward to seeing some practical utilities. Here's few suggestions: "Pop-up" help screens for use in MAC/65, Action! or BASIC. An 8k RAM cache. A "pop-up" calculator. A resident DUP.SYS. LATE NOTES I recently was browsing through some old computer magazines and came across an article by Bill Wilkinson dealing with 130XE RAM control. In it he stated that 16k Atari cartridges such as Atari Writer Plus occupy the address space from $8000 to $BFFF rather than using bank switching. As a final addition to this text, I've tacked on part 4 which is MAC/65 source code to produce two simple COM files to dump cartridges to disk for examination. The programs are not sophisticated but will do the trick. Revision: 16 Feb 96 LATER NOTE A message from Bill Wilkinson on comp.sys.atari.8bit has been added as RAMROM.TX5. It validates much of what has been said and reveals a bit more of cartridge construction. His memory's a bit off on address ranges, but the stuff about 4k banking is right on. I dumped MAC/65 and Action! to disk files and verified that, in each cartridge, the code from $B000-$BFFF is identical in all three banks. In effect, this indicates a mapping as follows (using MAC/65 as an example): Addr: $D500 $D501 $D509 $D508 $A000 +----+ +----+ +----+ +----+ c0 c1 c9 RAM $AFFF +----+ +----+ +----+ +----+ $B000 +----+ +----+ +----+ +----+ cc cc cc RAM $BFFF +----+ +----+ +----+ +----+ Where: "Addr" is the access address used to enable the configuration, "c0" etc. is the switchable 4k bank and "cc" is the common 4k bank. Substituting "3" and "4" for "1" and "9" above would produce a map of the Action! cartridge. Revision: 21 Aug 96 LATER, LATER NOTE I recently saw a message on c.s.a.8 by Glenn Saunders indicating that the MUX could be used transparently with cartridges. Unfortunately, he did not indicate if he knew how it was done. Update: 09 Apr 97