How to build a read/write cart-port interface for Atari computer
Toronto. April 14, 1987.
README -- version 1.0
This directory contains the files you need to put together an instruction manual on how to build a read/write cart-port interface.
The files include:
- cart.doc -- The document that describes how to build it and how it works.
- cart.cs2 -- A b/w degas picture encoded with Moshe Braner's Scode. The picture contains a schematic of the interface.
- cart.s -- Assembly language subroutines to read and write to the port. While reading this port is straightforward, writing isn't, so you might need these routines. You can link these in with software you write.
- poke.c -- Test program to exercise the hardware you have built.
- cart.pal -- PAL equations to burn the PAL. If you are not near a PAL burner, translate these equations into logic and use discrete logic chips. The equations are pretty straightforward to translate.
All the best!
--
Anees Munshi @ University of Toronto Engineering.
ARPA asm%csri.toronto.edu@csnet-relay.arpa
BitNet asm@utcsri.UTORONTO
CSNet asm@csri.toronto.edu
UUCP {allegra,ihnp4,decvax,pyramid}!utzoo!utcsri!asm
Reality is so much better!
Abstract
The Atari ST's 128 Kbyte read-only cartridge port can be transformed into a 64 Kbyte read/write port by using this circuit. Writing to this port is slower than reading it by a factor of two, approximately. It can be written to at a speed of 76 Kilowords per second.
This interface can be built by using only three chips, two AS-TTLs and a PAL -- a total parts cost of less than $15.
How It works
1. Background Information
The ST brings out 15 address lines (A1-A15), 16 data lines (d0- D15) /LDS,[*] /UDS, /AS, /ROM4 and /ROM3.
Of these signals, /UDS, /LDS, and /AS have the same meaning as they as they do for a 68000 CPU. A1-A15 are the least significant 15 address lines. D0-D15 are connected internally to the ST's data-bus. /ROM3 and /ROM4 are generated by the MMU and are output signals which tell the external hardware which of the two banks is being addressed. /ROM4 is activated when the 68000 tries to read address 0xfa0000-0xfaffff. /ROM3 is activated when the 68000 tries to read address 0xfb0000-0xfbffff. (Note that the names of these signals are counterintuitive; ie. they have been correctly written above.)
2. Caveats
Any and all hardware connected to this modified port must be fast -- fast enough to run with no wait-states and fast enough to tolerate the decoding delays introduced by this circuit. I've used 200ns static RAM chips without any trouble.
It is advisable to use /ROM4 to time any external hardware you build (in preference to /AS), since /ROM4 contains the necessary delay to ensure the addresses are stable when it is activated. (At least this is what I assumed, and it worked.)
The data for write operations is produced by the two AS-TTL latches (A preceeding slash indicates an active-low signal) used in the circuit (see below.)
3. Operating Principle
- The PAL contains an asynchronous finite state machine. When the upper bank (address 0xfbxxxx) is read, the PAL manufactures an A0 (based on the state of /UDS and /LDS) and supplies this to U1. At the end of the read cycle, U1 and U2 are clocked to latch the least significant 16 address bits into the PAL.
- Subsequently, when the lower bank is read (address 0xfaxxxx), the latched address (which was latched in step 1) is supplied as data and /PWE (the write-enable signal) is activated (it goes low). /ROM4 active should tell the external hardware that it is being addressed, and /PWE active should tell it that this is a write-cycle. As an interest- ing side-effect of this design, the data word written out is read back by the 68000 (you can use this fact to debug the interface!)
- Reads to the upper bank will return garbage unless some ROM is present there. Multiple successive reads to this bank will not be harmful (ie. they will not confuse the state- machine) and the latest address will be latched preparation for a read to the lower bank.
Note that if you put RAM in the upper-bank you will not be able to write to it. Connecting the /PWE line to such RAM will have no effect. Putting a ROM in this address space should not create any problems (although I haven't tried this).
Example:
To write data 0xcd01 to address 0x3bc0 in the lower bank, the following steps are performed.
step1. read byte at address 0xfbcd01 (0xfb0000+data)
step2. read word to address 0xfa3bc0 (0xfa0000+addr)
[*] I used AS374s, you can use AS or FAST. High speed chips are necessary so that delays from /OE (latch output-enable) to valid data are not critical. You could possibly get away with ALSTTL, but I haven't tried this.
Construction
The schematic should help in this department. Constructing this gadget is easy and cheap. The hardest part might be trying to find a connector for this port. You can get one from
Douglas Electronics,
718 Marina Blvd.,
San Leandro, Calif
94577
1-415-483-8770
(part number 33-DE-40. $10.00 per piece)[*]
Another problem you might face is getting the PAL programmed. If you don't know of a way to get the PAL programmed, you could use discrete logic chips to build the state machine.
4. Building it
The pin-outs given in the User's guide are CORRECT. The pin-outs given in the "Internals" book are WRONG. Follow the schematic. You can get the pin-outs for the '374s from an ASTTL or FAST databook. The PAL16L8's pin-outs are given below.
[*] You guessed it -- I'm not connected with D.E. although I and a number of others have purchased these connectors from them.
PAL PINOUTS
Legend:
- A preceding slash implies an active-low signal.
- NC implies no-contact (nothing should be connected to these pins.)
- (i) indicates that the signal is an input.
- (o) indicates that the signal is an output.
- (A) implies that the signal comes from/goes to the Atari cart. port.
- (u1) implies that the signal goes to U1, the least-significant latch.
- (u2) implies that the signal goes to U2, the most-significant latch.
- (ex) implies that the signal goes to any external hardware connected such as a ram-disk.
pin # signal pin # signal
----- ------ ----- ------
1 /UDS (i, A) 11 NC (no contact)
2 /LDS (i, A) 12 /OE (o, U1, U2)
3 /ROM4 (i, A) 13 A0 (o, U1)
4 /ROM3 (i, A) 14 /PWE (o, ex)
5 NC 15 NC
6 NC 16 /dclk (o, U1,U2)
7 NC 17 NC
8 NC 18 NC
9 NC 19 NC
10 gnd (A) 20 Vcc (A)
Note that the line called "help" is shown as "NC" since it is to be left unconnected.
ALL THE BEST!!
Anees Munshi. April 14, 1986.
ARPA asm%csri.toronto.edu@csnet-relay.arpa
BitNet asm@utcsri.UTORONTO
CSNet asm@csri.toronto.edu
UUCP {allegra,ihnp4,decvax,pyramid}!utzoo!utcsri!asm
58 York Road, Weston, Ontario M9R 3E6. Canada. (416) 241-2166
CART.S
/ module name CART.S %W% of %G%
/
/ contains:
/ writecart 16 : write word to cartridge port.
/ writecart 8 : write byte to cartridge port.
/ readcart 16 : read word from cartridge port.
/ fillmem : fill shared memory with a pattern.
/
.prvd
/---------------------------------------------------------------------
/ WRITECART16: WRITE 16 BIT DATA TO ADDRESS ON CART PORT
/---------------------------------------------------------------------
.shri
.globl writecart16_
writecart16_:
link a6, $0
clr.l d0
move 10(a6), d0 / fetch data to write
addi.l $0xFB0000, d0 / add it to base addr of ROM 3
movea.l d0, a0 / generate addr1 (say)
move.b (a0), d0 / read addr1 to latch data.
clr.l d0
move 8(a6), d0 / now take address to write data to
addi.l $0xFA0000, d0 / add this to base addr of ROM 4
movea.l d0, a0 / call this addr2 (say).
move (a0), d0 / read addr2 to write out data
unlk a6
rts / as side-effect, same data will be
/ returned!
/---------------------------------------------------------------------
/ READCART16: READ 16 BIT DATA FROM ADDRESS ON CART PORT
/---------------------------------------------------------------------
.shri
.globl readcart16_
readcart16_:
link a6, $0
clr.l d0
move 8(a6), d0 / fetch address to read from
addi.l $0xFA0000, d0 / add it to base addr of ROM 4
movea.l d0, a0 / generate addr1 (say)
move (a0), d0 / read addr1 to latch data.
unlk a6
rts / as side-effect, same data will be
/ returned!
/---------------------------------------------------------------------
/ WRITECART8: WRITE 8 BIT DATA TO ADDRESS ON CART PORT
/---------------------------------------------------------------------
.shri
/
/ writecart8(addr, data)
/ int addr, data
/
.globl writecart8_
writecart8_:
link a6, $0
clr.l d0
move 8(a6), d0 / fetch address
and $0xFFFE, d0 / and with 0xFFFE to evenify
move.l d0, d1
cmp.w 8(a6), d0
beq evn
odd: / this is write byte to odd address
move.w d0, 8(a6)
addi.l $0xFA0000, d1 / generate real even address
movea.l d1, a0
move.w (a0), d0 / fetch word
andi.w $0xFF00, d0 / null odd byte of this word
move.w 10(a6), d1
andi.w $0x00FF, d1 / null even byte of data
or.w d1, d0 / merge together
move d0, -(a7)
move 8(a6), -(a7) / and write to even address
jsr writecart16_
addq.l $4, a7
unlk a6
rts
evn: / even address continue here
move.w d0, 8(a6)
addi.l $0xFA0000, d1 / generate real even address
movea.l d1, a0
move.w (a0), d0
andi.w $0x00FF, d0
move.w 10(a6), d1
asl.w $8, d1
andi.w $0xFF00, d1
or d1, d0
move d0, -(a7)
move 8(a6), -(a7)
jsr writecart16_
addq.l $4, a7
unlk a6
rts
/----------------------------------------------------------------------
/ FILLMEM : FILL SHARED RAM WITH A PATTERN
/----------------------------------------------------------------------
.shri
.globl fillmem_
fillmem_:
link a6, $0
move 8(a6), d1 / this is the fill pattern (a 16 bit word)
clr.l d0 / start filling from addr 0 offset from
/ 0xFA0000
floop: move d1, -(a7) / push data (fill pattern) on stack
move d0, -(a7) / push address on stack
jsr writecart16_ / write fill pattern
move (a7)+, d0
move (a7)+, d1 / pop stack
addq.l $2, d0 / increment address
cmp $0x1000, d0 / is it the end of the 2K word memory
bge fdone
bra floop
fdone:
unlk a6
rts
/-----------------------------------------------------------------------
/ RUN320: TAKE TMS32010 OUT OF RESET MODE
/-----------------------------------------------------------------------
.shri
.globl run320q_
run320q_:
link a6, $0
movea.l $0xFA1000, a0 / address of the control register
move.w (a0), d0 / fetch contents of control register
andi.w $0x00FF, d0 / mask off contents of 320 port 1
ori.w $0x0001, d0 / force only reset to 1
move.w d0, -(a7) / push data to write in stack
move.w $0x1000, -(a7) / push address offset to write to in stack
jsr writecart16_
addq.l $4, a7 / remove arguments from stack
movea.l $0xFA0000, a0 / read the control register again
move.w (a0), d0 / keep value in a0 (ie return it)
unlk a6
rts
/-----------------------------------------------------------------------
/ STOP320: PUT TMS32010 IN RESET MODE
/-----------------------------------------------------------------------
.shri
.globl stop320_
stop320_:
link a6, $0
movea.l $0xFA1000, a0 / address of the control register
move.w (a0), d0 / fetch contents of control register
andi.w $0xFFFE, d0 / force reset bit to 0.
move.w d0, -(a7) / push data to write in stack
move.w $0x1000, -(a7) / push address offset to write to in stack
jsr writecart16_
addq.l $4, a7 / remove arguments from stack
movea.l $0xFA0000, a0 / read the control register again
move.w (a0), d0 / keep value in a0 (ie return it)
unlk a6
rts
POKE.C
/*
* poke.c Poke data into address on cart port.
*
* compile: cc poke.c cart.s
*/
#include <stdio.h>
#define ROM3_BASE 0xFB0000L
#define ROM4_BASE 0xFA0000L
/*
* writecart16(addr, data) in cart.s
* unsigned addr; 0..65535
* int data;
*
* -- Writes 16 bit data to ROM4 space on cart port.
* -- Address supplied is referred to ROM4_BASE. Data is written there.
*
* eg. writecart16(0xC000, 0xFFFF), will write
* 16 bit word 0xFFFF to location ROM4_BASE + 0xC000.
* eg. writecart16(0xC001, 0xFFFF) will bomb because addr is odd.
*/
extern int writecart16();
main(argc, argv)
int argc;
char **argv;
{
char s[10];
unsigned addr;
unsigned data;
register unsigned i;
if ( (argc != 2) && (argc != 3) && (argc != 4) ) {
printf("\tUsage: poke [r|w] [addr] <[data]>.\n");
printf("\t[addr] and [data] are to be 16 bit decimal numbers.\n");
exit(0);
}
if (argv[1][0] == 'w') {
addr = atoi(argv[2]);
data = atoi(argv[3]);
printf("Writing 0x%4x to 0xFa0000+0x%4x\n", data, addr);
for (i=0L; ; ++i) {
writecart16(addr, data);
if (i%10000L == 0) {
i = 0L;
printf("*");
fflush(stdout);
}
}
exit(0);
}
else if (argv[1][0] == 'r') {
addr = atoi(argv[2]);
printf("0xFA0000 + %x contains %x.\n",
addr, * (int *) (ROM4_BASE + addr) );
exit(0);
}
else {
printf("Unknown command '%c'. Must be 'r' or 'w'.\n",
argv[1][0]);
exit(-1);
}
}
CART.PAL
pal16l8
/uds /lds /rom4 /rom3 nc nc nc nc nc gnd
nc /oe /a0 /pwe /help /dclk nc nc nc vcc
; Version 2. Hopefully with the A0 problem corrected
; PWE is the write-enable. It is called PWE since it represents a PENDING
; WRITE ENABLE!
pwe = /pwe * rom3 + pwe * rom3 + pwe * /rom3 * /rom4 * /help +
pwe * rom4
help = rom4 * pwe + help * /rom4 * pwe
a0 = /a0 * rom3 * uds + a0 * /rom4
dclk = rom3 * lds + rom3 * uds
oe = rom4 * pwe
function table