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 */