[net.micro.6809] Vectrex revealed!

wje@sii.UUCP (Bill Ezell) (04/26/84)

b
Here is almost everything that you ever wanted to know about the
Vectrex display. First, some introductory information.

The processor used in the display is a 6mhz 68A09, which is a
pretty good processor for an 8-bitter.

The address space is split up as follows:

   0000
    .
   C7FF  address space available to external rom or ram

   C800
    .
   CBFF  internal ram (1k)

   D004  PIA timer 1 low order byte (sets magnification factor)
   D008  PIA timer 2 low order byte
   D009  PIA timer 2 hi order byte
   D00A  PIA shift register

   E000
    .
   EFFF  Mine Storm game (4k)

   F000
    .
   FFFF  'executive' routines (4k)

Several locations in the ram are used by the system rom, and you need
to know about some of them. What they do in more detail will be described
in the individual routine writeups.

Here are some important addresses:

   C80F-11	used by switches() routine, don't touch
   C812-19	state of buttons on both consoles
   C81A		conversion resolution for joystick
   C81B-E	values of the 4 joystick pots
   C81F-22	enable flags for joystick pots
   C823		joystick mode; <0, successive approx., >=0 l/r/u/d
   C824		check0ref enable flag, 0 -> no, else yes
   C825,6	waitrecal call counter
   C827		current z axis value, set by zaxis routines
   C828		dot dwell time
   C829		blanking pattern to shift out during drawing
   C82A,B	used to store 1st 2 bytes of param block by printu()
   C82C,D	used to save ureg by printu()
   C837,8	used to store cartridge address ptr during bootup
   C83B		if 0, don't display score on reboot, else show score
   C83D,E	t2 reload value, t2-l first. x3075 is 20 msec
   C83F-4C	14 byte buffer, used to output to sound chip
   C84D		used by sound chip routines
   C84F,0	saves first two bytes of a music block
   C851,2	saves next two bytes of a music block
   C853,4	save ptr pointing to 5th byte of music block
   C856		1 when music ready, x80 when music rtn is called, 0 when done
   C85E
   C87D		incremented by restart
   CBEA		sp is set here by boot
   CBEB-F0	(6 bytes), holds current score in ascii form
   CBF1		terminates above string, don't use
   CBF2		swi 2,3 vectors here
   CBF5		firq vectors here
   CBF8		irq vectors here
   CBFB		swi, nmi vectors here
   CBFE,F	checked by reset for x7321, decides whether this is cold boot

   reset clears C800->7A

Before getting into the routines, a little discussion of the hardware
is appropriate.

In general, all vectors are drawn relative to the last beam position.
Two values, the relative coordinates to move to, are sent to a d/a and
loaded into the y and x sample/holds (there isn't really one for x,
but pretend that there is). These values are applied to integrators,
which when enabled will cause the beam to move at a rate proportional
to the values times the duration of integration. What this means is
that for a given set of vectors, the drawn object can be zoomed just
by changing the integration time. The PIA timer 1 is used to control
integration time.  One disadvantage of this method is that accumulated
integration errors can become large. It's necessary to recalibrate the
integrators every now and then, and a routine is provided to do this.

PIA timer 2 is used for determining refresh rate, but may be used by
application code with a little care.

The shift register in the PIA is used to control the beam blanking
pattern when drawing vectors; this allows patterned lines to be
drawn.

The sound chip is an unknown area. If anyone has any information
on the AY3-8192 chip from General Instruments, I'd appreciate it.

Ok, now for some stuff on using your own proms and writing your
own stuff for the Vectrix.

The rom in the game cartridges is compatible with 2532's, with
the minor exception that pin 21, which is Vpp on the 2532, is
an additional enable on the rom, enabled by logic 0. (~enable)
This is inconvenient since pin 20, ~E, is driven by the processor
E signal. This means that you need to do a little additional
decoding, or use a prom with 2 enable inputs, such as a 2716,
which only gives you 2K. In any case, all processor lines are
available at the cartridge connector, so you can do whatever you
want.

Ok, on the the good stuff.

When the Vectrex boots, it puts up its little intro and then looks
at location 0000-000A for the string 'g GCE ????' followed by 0x80,
where '?' is any character. Why 'g'? The Vectrix uses lower case
characters to select special symbols. 'g' is the copyright symbol.
If this isn't found, it runs 'Mine Storm'.
If it is found, it expects the next two bytes to be a pointer to a
music block (more later), followed by a string block.

A string block is used by several routines to display strings of text.
It consists of a 4 byte header, followed by the ascii characters,
terminated by 0x80. More string blocks may immediately follow. The last
block must have 0x00 after it.
The first byte of the header is the height of the
displayed string, the second is the width, the third is the relative
y coordinate of the start of the string, the fourth is the relative x
coordinate. I should note that the coordinate system used has (0,0) at
the center of the screen, +y is up, +x is right. Remember, however, that
all vectors are drawn relative to the last beam position.
The height and width bytes interact to some extent, and sometimes changing
the width disproportionally to the height will give slanted characters.
The two size bytes are signed, so it is possible to draw backwards and/or
upside down characters. The  y,x coordinate corresponds to the upper
left-hand corner of the first character, so to get a normal string, you
need to use a negative height (draw down from upper left hand corner) and
a positive width (draw right from upper left hand corner). The vertical
size covers a much greater size range than the horizontal; play around a bit.
The sizes used in the 'MINE STORM' string are -8, 0x50 respectively.

Immediately after the string block is the first instruction to execute.
To recap, a valid rom looks like this:

	.org	0;
	.byte	'g GCE 9999', 0x80;	/* special case, needs no 0x0 */
	.word	music_block_addr;
	.byte	0xf8,0x50,0x20,0xe8,'MY ROM',0x80,0x0;
	first executable instruction

A music block consists of a 4 byte header, followed by pairs of bytes.
The first two bytes are the address of a parameter block that configures
the sound chip to simulate various things, the next two bytes I have no
idea about. The sequence '.word 0xED8F, 0xFEB6;' works.	The byte pairs
consist of a note and a duration. The note scale starts at 'C', which
is 0, and goes up. It includes chromatics; C# is 1, D is 2, etc.
The note list is terminated by 0x0, 0x80;

The following will play 'Mary had a little lamb':

music:	.word	0xED8F;
	.word	0xFEB6;
	.byte	4,30, 2,10, 0,20, 2,20, 4,20, 4,20, 4,40;
	.byte	0, 0x80;			/* end of music */

After the boot code has determined that you have a valid rom, it plays
your music, displays your text string, your copyright notice, and then
jumps to the first instruction in your cartridge. There is unfortunately
no way around this nonsense without making a new exec rom.

Ok, now that you are booted, here are some routines, entry points, etc.
that let you do real stuff:

check0ref	0xF34F
   If 0xC824 is non-zero, call reset0ref (see below), else return.
   Dp is assumed to be D0. Many of the line drawing routines
   call this guy, so if you want to be sure that your dot position
   doesn't magically go to (0,0), be sure 0xC824 is 0.

dotixb		0xF2BE
   Draw a dot at the rel. y, rel. x coordinates pointed to by the x
   register, with an intensity proportional to the value in the b
   register. The x register is incremented by 2. Dp is assumed to
   be D0.

dptoC8		0xF1AF
   The dp register is set to C8, direct addresses start at C800.
   The a register is trashed.

dptoD0		0xF1AA
   The dp register is set to D0, direct addresses start at D000.
   This is used to access the PIA.
   The a register is trashed.

drawl1		0xF40C
   Draw a list of vectors. The x register has a pointer to a vector list
   of the following form:
	.byte integration time (scale factor or magnification)
	followed by byte triplets of the form:
	.byte mode, rel. y coord., rel. x coord
	where mode:
	 < 0 visible vector
	 = 0 invisible vector
	 > 0 end of list, return to caller
   The dp register is assumed to be set to D0.

drawl1b		0xF40E
   Draw a list of vectors, just like above, except the magnification
   is in the b register, not in the vector list.

drawl2		0xF46E
   Draw a list of vectors, with a few added twists. This routine does not
   set or change the magnification factor, it is whatever is currently
   in t1-l in the PIA. It uses the same triplet structure as above, except
   the mode byte values, which are:
      < 0 use value in C829, 0 is invisible, 0xFF is visible
      = 0 invisible
      = 1 end of list, return to caller
      > 1 visible

joystick	0xF1F8
   Read the joystick positons of the two consoles. This routine requires
   a fair amount of setup before calling. First, C81F-22 are enable flags.
   EVERY ONE must be set to one of:
      0   ignore
      1   pot 0 (console 1 left/right)
      3   pot 1 (console 1 up/down)
      5   pot 2 (console 2 left/right)
      7   pot 3 (console 2 up/down)
   Locations C81B-1D will be set with the pot values corresponding to the
   enable flags.
   Location C823 determines what kind of input conversion to do.
   If it is >= 0, the return value will be <0, 0, >0 depending upon
   whether the joystick is left (or down) of center, centered, or
   right (up) of center.
   If it is < 0, a successive approximation algorithm is used to read the
   actual value of the joystick pot, a signed value. In this case, C81A
   must bet set to a power of 2 to control conversion resolution. 0x80 is
   least accurate, 0x00 is most accurate. Any value other than a power
   of 2 is treated as 0. Dp is assumed to be set to D0. The routine clears
   C823 before returning!

move170u	0xF308
   Move the (invisible) dot. The x register points to a byte pair, rel. y
   and rel. x, to move to. The integration time is set to 170 usec. This
   routine leaves t1-l set to 0xFF (170 usec). Dp is assumed to be D0.
   The x register is incremented by 2 before returning.

move85u		0xF30C
   Same as above, except t1-l is set to 0x7F (85 usec).

moved		0xF312
   Move the (invisible) dot to the rel. y in the a register, rel. x in
   the b register. The integration time is whatever was last set in t1-l,
   which is left unchanged. Dp is assumed to be D0.

moveix		0xF310
   Same as move170u, except the current value of t1-l is used, and is
   left unchanged, like moved above. Dp is assumed to be D0.

printu		0xF385
   Display the string whose string block is ptd to by the u register.
   See above a bit for what a string block is. Dp is assumed to be D0.

reset0ref	0xF354
   This routine zeros the integrators and the reference cap for them.
   Call this every once in a while during a complex set of vector drawing
   to keep everything matched up. It resets the origin to (0,0).
   This routine leaves the integrators in zero mode, so you can't draw
   anything until you do a move, or set 0xD00C to 0xCE. Dp is assumed
   to be D0;

startt2		0xF1A2
   This routine loads the refresh timer, t2, with the value in C83D,E
   and recalibrates the vector generators, leaving the dot off at
   location (0,0), the center of the screen. Note that C83D is loaded
   into t2-l, C83E into t2-h. T2 decrements at 1.5 mhz; a value
   of 0x3075 in C83E will give a refresh rate of 50 hz.
   Dp is assumed to be D0.

switches	0xF1B4
   Read the switch values of the two consoles. Locations C812-19 are
   set, corresponding to console 1, switch 1,2,3,4, console 2 switch 1,2,3,4.
   Before calling the routine, the a register must contain a bit mask,
   bit 0 corresponding to cons. 1, sw 1, etc. If a bit is 0, the current
   value of the switch is returned in the appropriate location, 1 for down,
   0 for up. If a bit is 1, the return location is set to 1 only on the
   depression transititon of the button. Additional calls will return 0 until
   the switch is released, and then depressed again. Dp is assumed to be
   set to D0.

waitrecal	0xF192
   This routine waits for t2 to time out, reloads it from 0xC83D, and
   then recalibrates the vector generators to (0,0). You MUST call
   this routine once every refresh cycle, or your vectors will go
   bonzo, due to accumulated integration errors in the hardware.
   This routine calls reset0ref, so the integrators are left in zero
   mode. Dp is assumed to be D0.

zaxto1F		0xF29D
zaxto3F		0xF2A1
zaxto5F		0xF2A5
zaxto7F		0xF2A9
   These routines set the z axis (intensity) sample/hold to 0x1F, 0x3F,
   etc. The z axis should be reloaded at least every refresh cycle; you
   can certianly change it on the fly. The current value is saved in
   0xC827. Dp is assumed to be D0.

zaxtoa		0xF2AB
   Same as above, except the z axis value to use is in the a register.

There are many more entry points; if anyone is interested, let me know
and I'll prepare another article. Also, I have a 6809 assembler that
runs under Unix and produces Intel hex format output. If anyone wants
it, I'll provide it gratis. If enough people want it, I'll post it.

   Bill Ezell
   Software Innovations, Inc.
   (decvax,ittvax)!sii!wje
   (603) 883-9300

The following program draws an expanding square with its lower left hand
corner at the center of the screen:

#define dptoC8		0xF1AF
#define dptoD0		0xF1AA
#define drawl1b		0xF40E
#define waitrecal	0xF192
#define xaxto7F		0xF2A9

#define mag	0xC900

	.text;
	.org	0x0;
	.byte	'g',' ','G','C','E',' ','2','0','0','1',0x80;
	.word	music;
	.word	0xf850;
	.word	0x30e8;
	.byte	'B','O','X', 0x80,0;

start:	clr	mag;				/* start with smallest box */
	ldd	#0x3075;
	std	0xC83D;				/* set t2 timer for 20 msec */
	jsr	dptoD0;
	std	0x8;				/* start t2 */

loop:	jsr	waitrecal;			/* wait for end of cycle */
	jsr	xaxto7F;			/* adjust beam intensity */
	lda	#0xCE;
	sta	0xC;				/* get out of zero mode! */
	inc	mag;				/* grow box once each cycle */
	ldb	mag;
	ldx	#box;
	jsr	drawl1b;			/* xreg points to dlist now */
	bra	loop;

boxloc:	.byte	0,0;
box:	.byte	-1,0,50;
	.byte	-1,50,0;
	.byte	-1,0,-50;
	.byte	-1,-50,0;
	.byte	1;

/* the startup stuff points to this as our initial music */

music:
	.word	0xed8f;
	.word	0xfeb6;
	.byte	4,30, 2,10, 0,20, 2,20, 4,20, 4,20, 4,40;
	.byte	0, 0x80;			/* end of music */