SEGA Mouse

For anything related to IO (joypad, serial, XE...)

Moderator: BigEvilCorporation

Post Reply
Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

SEGA Mouse

Post by Chilly Willy » Wed Jun 03, 2009 7:14 am

I just bought Wacky Worlds + Mouse on eBay. I figured having mouse support for Wolf3D and Doom would make some people happy. Anywho, that meant digging out info on the mouse. I started with Charles MacDonalds doc on the Genesis IO, where you find this:

Code: Select all

Sega Mega Mouse

This is a three button mouse (left, middle, right) with an extra button (Start) located near the thumb position. It uses a PIC16C54
microcontroller which manages the buttons and position tracking.
The Sega Mega Mouse that is distributed in North America is incompatible with the European version of Populous 2. The mouse
functions normally but has Y axis inverted so up is down and vice-versa. It may have been designed for the Japanese Mega Drive
mouse, which is a different product with 2 buttons and unique physical appearance.
The protocol works as follows: TH and TR are outputs which tell the microcontroller to stop or start a data transfer, and to
acknowledge received data. TL is an input which returns a busy flag for the microcontroller. D3-D0 are inputs that return the data.

Here's a table showing the communication process:
 Write   TH   TR   TL   D3       D2       D1        D0      Description
 $60     1    1    1    0        0        0         0       Request data
 $20     0    1    1    0        0        0         0       ID #0 ($0)
 $00     0    0    1    1        0        1         1       ID #1 ($B)
 $20     0    1    0    1        1        1         1       ID #2 ($F)
 $00     0    0    1    1        1        1         1       ID #3 ($F)
 $20     0    1    0    Y Over   X Over   Y Sign    X Sign  Axis sign and overflow
 $00     0    0    1    Start    Middle   Right     Left    Button state
 $20     0    1    0    X7       X6       X5        X4      X axis MSN
 $00     0    0    1    X3       X2       X1        X0      X axis LSN
 $20     0    1    0    Y7       Y6       Y5        Y4      Y axis MSN
 $00     0    0    1    Y3       Y2       Y1        Y0      Y axis LSN

Write #$60 when you are done polling to stop the data transfer. If you continue to poll the mouse after collecting all the data by
writing $20 / $00, the Y axis LSN will always be returned. Sega advises polling beyond this point will make the mouse behave
abnormally. It's possible that the PIC code in some versions of the Mega Mouse may have problems if the poll sequence is too
long.
The X/Y overflow flags are supposed to be set if the mouse is moved over a distance greater than can be measured. I can't get
them to become set, maybe the mouse supports a fairly wide range of movement.
You need a considerable delay between writing new values to TH/TR. I think the intention is to poll TL until the microcontroller
returns 'not busy', but I can't get this to work reliably. Sega's mouse reading code also has a timeout when checking TL which
would indicate it may get stuck in a certain state.
All buttons (start, left, middle, right) are active-high logic, so they return '1' when pressed and '0' when released.
It doesn't really say what kind of delay is needed to read the mouse, and there's that bit on TL being a "busy" flag... I wanted to see some code, so I disassembled the read routine in Wacky Worlds.

Code: Select all

00B640	CMPI.W	#$1,D0
00B644	BLE.B	START+$B648 ; port not out of range
00B646	RTS

00B648	MOVE.W	#$100,($A11100)
00B650	MOVE.W	#$100,($A11200)
00B658	BTST	#$0,($A11100)
00B660	BNE.B	START+$B658 ; wait on Z80 stopped

00B662	ADD.W	D0,D0
00B664	MOVEA.L	#$A10003,A0
00B66A	ADDA.W	D0,A0
00B66C	MOVE.B	#$60,($6,A0) ; set direction bits
00B672	NOP
00B674	NOP
00B676	MOVE.B	#$60,(A0) ; restart mouse packet
00B67A	NOP
00B67C	NOP
00B67E	BTST	#$4,(A0)
00B682	BEQ.B	START+$B67E ; wait on handshake
00B684	MOVE.B	(A0),D0
00B686	ANDI.B	#$F,D0
00B68A	BNE.W	START+$B78E ; not 0 means not mouse
00B68E	MOVE.B	#$20,(A0)
00B692	MOVE.W	#$FE,D1
00B696	BTST	#$4,(A0)
00B69A	BNE.B	START+$B6A4 ; handshake
00B69C	DBRA	D1,START+$B696 ; wait on handshake
00B6A0	BRA.W	START+$B780 ; timeout err

00B6A4	MOVE.B	(A0),D0
00B6A6	ANDI.W	#$F,D0
00B6AA	MOVE.B	#$0,(A0)
00B6AE	CMPI.W	#$B,D0
00B6B2	BNE.W	START+$B780 ; not $B means not mouse
00B6B6	BTST	#$4,(A0)
00B6BA	BEQ.B	START+$B6C4 ; handshake
00B6BC	DBRA	D1,START+$B6B6 ; wait on handshake
00B6C0	BRA.W	START+$B780 ; timeout err

00B6C4	MOVE.B	(A0),D0 ; should be $F
00B6C6	NOP
00B6C8	MOVE.B	#$20,(A0)
00B6CC	NOP
00B6CE	BTST	#$4,(A0)
00B6D2	BNE.B	START+$B6DC ; handshake
00B6D4	DBRA	D1,START+$B6CE ; wait on handshake
00B6D8	BRA.W	START+$B780 ; timeout err

00B6DC	MOVE.B	(A0),D0 ; should be $F as well
00B6DE	NOP
00B6E0	NOP
00B6E2	MOVE.B	#$0,(A0)
00B6E6	MOVEQ	#$0,D0
00B6E8	NOP
00B6EA	BTST	#$4,(A0)
00B6EE	BEQ.B	START+$B6F8 ; handshake
00B6F0	DBRA	D1,START+$B6EA ; wait on handshake
00B6F4	BRA.W	START+$B780 ; timeout err

00B6F8	MOVE.B	(A0),D0 ; axis sign/overflow
00B6FA	MOVE.B	#$20,(A0)
00B6FE	LSL.W	#$8,D0
00B700	BTST	#$4,(A0)
00B704	BNE.B	START+$B70C ; handshake
00B706	DBRA	D1,START+$B700 ; wait on handshake
00B70A	BRA.B	START+$B780 ; timeout err

00B70C	MOVE.B	(A0),D0 ; buttons
00B70E	MOVE.B	#$0,(A0)
00B712	LSL.B	#$4,D0
00B714	LSL.L	#$8,D0
00B716	BTST	#$4,(A0)
00B71A	BEQ.B	START+$B722 ; handshake
00B71C	DBRA	D1,START+$B716 ; wait on handshake
00B720	BRA.B	START+$B780 ; timeout err

00B722	MOVE.B	(A0),D0 ; x axis hi nibble
00B724	MOVE.B	#$20,(A0)
00B728	LSL.B	#$4,D0
00B72A	LSL.L	#$4,D0
00B72C	BTST	#$4,(A0)
00B730	BNE.B	START+$B738 ; handshake
00B732	DBRA	D1,START+$B72C ; wait on handshake
00B736	BRA.B	START+$B780 ; timeout err

00B738	MOVE.B	(A0),D0 ; x axis lo nibble
00B73A	MOVE.B	#$0,(A0)
00B73E	LSL.B	#$4,D0
00B740	LSL.L	#$4,D0 ; make x axis byte
00B742	BTST	#$4,(A0)
00B746	BEQ.B	START+$B74E ; handshake
00B748	DBRA	D1,START+$B742 ; wait on handshake
00B74C	BRA.B	START+$B780 ; timeout err

00B74E	MOVE.B	(A0),D0 ; y axis hi nibble
00B750	MOVE.B	#$20,(A0)
00B754	LSL.B	#$4,D0
00B756	LSL.L	#$4,D0
00B758	BTST	#$4,(A0)
00B75C	BEQ.B	START+$B764 ; handshake
00B75E	DBRA	D1,START+$B758 ; wait on handshake
00B762	BRA.B	START+$B780 ; timeout err

00B764	MOVE.B	(A0),D0 ; y axis lo nibble
00B766	MOVE.B	#$60,(A0)
00B76A	LSL.B	#$4,D0
00B76C	LSR.L	#$4,D0 ; make y axis byte
00B76E	BTST	#$4,(A0)
00B772	BEQ.B	START+$B76E ; wait on handshake
00B774	NOP
00B776	MOVE.W	#$0,($A11100) ; free z80 bus
00B77E	RTS

00B780	MOVE.B	#$60,(A0) ; timeout err -> restart mouse packet
00B784	NOP
00B786	NOP
00B788	BTST	#$4,(A0)
00B78C	BEQ.B	START+$B788 ; wait on handshake

00B78E	MOVEQ	#-$1,D0
00B790	MOVE.W	#$0,($A11100) ; free z80 bus
00B798	RTS
You can see that it makes a total of 255 retries over the entire packet, and that the "busy" flag is a toggling handshake line that may or may not be ready after two NOPs. It returns either -1 for error or a data packet for the mouse in D0.

Whew! That's one heck of a routine. :lol:

ob1
Very interested
Posts: 463
Joined: Wed Dec 06, 2006 9:01 am
Location: Aix-en-Provence, France

Post by ob1 » Wed Jun 03, 2009 9:09 am

It actually is !
Great work !

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Thu Jun 11, 2009 8:41 pm

Just some more info on how I use the mouse with the 32X... it should be fairly easy to modify this for use by the Genesis alone if you wanted that.

First, I detect the mouse shortly after initializing the Genesis VDP.

Code: Select all

        move.w  #0,0xA15128         /* controller 1 */
        move.w  #0,0xA1512A         /* controller 2 */
| look for mouse
        lea     0xA10003,a0
0:
        bsr     get_mky
        cmpi.l  #-2,d0
        beq.b   0b                  /* timeout */
        cmpi.l  #-1,d0
        beq.b   1f                  /* no mouse */
        move.w  #0xF001,0xA15128    /* mouse in port 1 */
1:
        lea     2(a0),a0
2:
        bsr     get_mky
        cmpi.l  #-2,d0
        beq.b   2b                  /* timeout */
        cmpi.l  #-1,d0
        beq.b   3f                  /* no mouse */
        move.w  #0xF001,0xA1512A    /* mouse in port 2 */
3:
0xA15128 and A are words in the 32X communications registers I use for telling the 32X the current controller value. You would change those to variables in the 68000 Work RAM for a Genesis only version.

Part of my vertical blank interrupt handler is reading the controllers.

Code: Select all

        /* read controllers */
        move.w  0xA15128,d0
        andi.w  #0xF000,d0
        cmpi.w  #0xF000,d0
        beq.b   0f                  /* no pad in port 1 (or mouse) */
        lea     0xA10003,a0
        bsr.b   get_pad
        move.w  d2,0xA15128         /* controller 1 current value */
0:
        move.w  0xA1512A,d0
        andi.w  #0xF000,d0
        cmpi.w  #0xF000,d0
        beq.b   1f                  /* no pad in port 2 (or mouse) */
        lea     0xA10005,a0
        bsr.b   get_pad
        move.w  d2,0xA1512A         /* controller 2 current value */
1:
Notice how I skip reading the controller if it's a mouse or not present. There is a separate command the 32X sends the 68000 to read the current mouse value.

Code: Select all

read_mouse:
        tst.b   d0
        bne.b   1f                  /* skip port 1 */

        move.w  0xA15128,d0
        andi.w  #0xF001,d0
        cmpi.w  #0xF001,d0
        bne.b   1f                  /* no mouse in port 1 */
        lea     0xA10003,a0
        bsr     get_mky
        bset    #31,d0
        move.w  d0,0xA15122
        swap    d0
        move.w  d0,0xA15120
0:
        move.w  0xA15120,d0
        bne.b   0b                  /* wait for SH2 to read mouse value */
        bra     main_loop
1:
        move.w  0xA1512A,d0
        andi.w  #0xF001,d0
        cmpi.w  #0xF001,d0
        bne.b   3f                  /* no mouse in port 2 */
        lea     0xA10005,a0
        bsr     get_mky
        bset    #31,d0
        move.w  d0,0xA15122
        swap    d0
        move.w  d0,0xA15120
2:
        move.w  0xA15120,d0
        bne.b   2b                  /* wait for SH2 to read mouse value */
        bra     main_loop
3:
        move.l  #-1,d0              /* no mouse */
        move.w  d0,0xA15122
        swap    d0
        move.w  d0,0xA15120
4:
        move.w  0xA15120,d0
        bne.b   4b                  /* wait for SH2 to read mouse value */
        bra     main_loop
For a Genesis only version, you'd simply return the long in d0 instead of sending it to the 32X. The 32X has a corresponding function to read this value.

Code: Select all

static int get_mouse(int port)
{
    unsigned int mouse1, mouse2;
    while (MARS_SYS_COMM0) ; // wait until 68000 has responded to any earlier requests
    MARS_SYS_COMM0 = 0x0500|port; // tells 68000 to read mouse
    while (MARS_SYS_COMM0 == (0x0500|port)) ; // wait for mouse value
    mouse1 = MARS_SYS_COMM0;
    mouse2 = MARS_SYS_COMM2;
    MARS_SYS_COMM0 = 0; // tells 68000 we got the mouse value
    return (int)((mouse1 << 16) | mouse2);
}
The read_mouse function above needs this function that actually gets the mouse packet.

Code: Select all

| get current mouse value
| entry: a0 = mouse control port
| exit:  d0 = mouse value (0  0  0  0  0  0  0  0  YO XO YS XS S  M  R  L  X7 X6 X5 X4 X3 X2 X1 X0 Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0) or -2 (timeout) or -1 (no mouse)
get_mky:
        move.w  sr,d2
        move.w  #0x2700,sr      /* disable ints */

        move.b	#0x60,6(a0)     /* set direction bits */
        nop
        nop
        move.b  #0x60,(a0)      /* first phase of mouse packet */
        nop
        nop
0:
        btst    #4,(a0)
        beq.b   0b              /* wait on handshake */
        move.b  (a0),d0
        andi.b  #15,d0
        bne     mky_err         /* not 0 means not mouse */

        move.b  #0x20,(a0)      /* next phase */
        move.w  #254,d1         /* number retries before timeout */
1:
        btst    #4,(a0)
        bne.b   2f              /* handshake */
        dbra    d1,1b
        bra     timeout_err
2:
        move.b  (a0),d0
        andi.b  #15,d0
        move.b  #0,(a0)         /* next phase */
        cmpi.b  #11,d0
        bne     mky_err         /* not 11 means not mouse */
3:
        btst    #4,(a0)
        beq.b   4f              /* handshake */
        dbra    d1,3b
        bra     timeout_err
4:
        move.b  (a0),d0         /* specs say should be 15 */
        nop
        nop
        move.b  #0x20,(a0)      /* next phase */
        nop
        nop
5:
        btst    #4,(a0)
        bne.b   6f
        dbra    d1,5b
        bra     timeout_err
6:
        move.b  (a0),d0         /* specs say should be 15 */
        nop
        nop
        move.b  #0,(a0)         /* next phase */
        moveq   #0,d0           /* clear reg to hold packet */
        nop
7:
        btst    #4,(a0)
        beq.b   8f              /* handshake */
        dbra    d1,7b
        bra     timeout_err
8:
        move.b  (a0),d0         /* YO XO YS XS */
        move.b  #0x20,(a0)      /* next phase */
        lsl.w   #8,d0           /* save nibble */
9:
        btst    #4,(a0)
        bne.b   10f             /* handshake */
        dbra    d1,9b
        bra     timeout_err
10:
        move.b  (a0),d0         /* S  M  R  L */
        move.b  #0,(a0)         /* next phase */
        lsl.b   #4,d0           /* YO XO YS XS S  M  R  L  0  0  0  0 */
        lsl.l   #4,d0           /* YO XO YS XS S  M  R  L  0  0  0  0  0  0  0  0 */
11:
        btst    #4,(a0)
        beq.b   12f             /* handshake */
        dbra    d1,11b
        bra     timeout_err
12:
        move.b  (a0),d0         /* X7 X6 X5 X4 */
        move.b  #0x20,(a0)      /* next phase */
        lsl.b   #4,d0           /* YO XO YS XS S  M  R  L  X7 X6 X5 X4 0  0  0  0 */
        lsl.l   #4,d0           /* YO XO YS XS S  M  R  L  X7 X6 X5 X4 0  0  0  0  0  0  0  0 */
13:
        btst    #4,(a0)
        bne.b   14f             /* handshake */
        dbra    d1,13b
        bra     timeout_err
14:
        move.b  (a0),d0         /* X3 X2 X1 X0 */
        move.b  #0,(a0)         /* next phase */
        lsl.b   #4,d0           /* YO XO YS XS S  M  R  L  X7 X6 X5 X4 X3 X2 X1 X0 0  0  0  0 */
        lsl.l   #4,d0           /* YO XO YS XS S  M  R  L  X7 X6 X5 X4 X3 X2 X1 X0 0  0  0  0  0  0  0  0 */
15:
        btst    #4,(a0)
        beq.b   16f             /* handshake */
        dbra    d1,15b
        bra     timeout_err
16:
        move.b  (a0),d0         /* Y7 Y6 Y5 Y4 */
        move.b  #0x20,(a0)      /* next phase */
        lsl.b   #4,d0           /* YO XO YS XS S  M  R  L  X7 X6 X5 X4 X3 X2 X1 X0 Y7 Y6 Y5 Y4 0  0  0  0 */
        lsl.l   #4,d0           /* YO XO YS XS S  M  R  L  X7 X6 X5 X4 X3 X2 X1 X0 Y7 Y6 Y5 Y4 0  0  0  0  0  0  0  0*/
17:
        btst    #4,(a0)
        beq.b   18f             /* handshake */
        dbra    d1,17b
        bra     timeout_err
18:
        move.b  (a0),d0         /* Y3 Y2 Y1 Y0 */
        move.b  #0x60,(a0)      /* first phase */
        lsl.b   #4,d0           /* YO XO YS XS S  M  R  L  X7 X6 X5 X4 X3 X2 X1 X0 Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 0  0  0  0 */
        lsr.l   #4,d0           /* YO XO YS XS S  M  R  L  X7 X6 X5 X4 X3 X2 X1 X0 Y7 Y6 Y5 Y4 Y3 Y2 Y1 Y0 */
19:
        btst    #4,(a0)
        beq.b   19b             /* wait on handshake */

        move.w  d2,sr           /* restore int status */
        rts

timeout_err:
        move.b  #0x60,(a0)      /* first phase */
        nop
        nop
0:
        btst    #4,(a0)
        beq.b   0b              /* wait on handshake */

        move.w  d2,sr           /* restore int status */
        moveq   #-2,d0
        rts

mky_err:
        move.b  #0x40,6(a0)     /* set direction bits */
        nop
        nop
        move.b  #0x40,(a0)

        move.w  d2,sr           /* restore int status */
        moveq   #-1,d0
        rts
So how do we handle this value we get? This C code shows how, and would be the same for 32X or Genesis only.

Code: Select all

void IN_GetMouseDelta(int *dx, int *dy)
{
    if (MousePresent)
    {
        if (dx)
            *dx = mousexaxis;
        if (dy)
            *dy = -mouseyaxis;
        mousexaxis = 0;
        mouseyaxis = 0;
        return;
    }
    if (dx)
        *dx = mx;
    if (dy)
        *dy = my;
}

byte IN_MouseButtons()
{
    static unsigned int lastmousetick = -1;

    if (MousePresent)
    {
        if (lastmousetick != MARS_SYS_COMM12)
        {
            int val = get_mouse(0);
            if (val != -2)
            {
                int mx, my;
                mousebuttons = (val >> 16) & 15;
                mx = (val >> 8) & 0xFF;
                // check sign
                if (val & 0x00100000)
                    mx |= 0xFFFFFF00;
                // check overflow
                if (val & 0x00400000)
                    mx = (val & 0x00100000) ? -256 : 256;
                mousexaxis += mx;
                my = val & 0xFF;
                // check sign
                if (val & 0x00200000)
                    my |= 0xFFFFFF00;
                // check overflow
                if (val & 0x00800000)
                    my = (val & 0x00100000) ? -256 : 256;
                mouseyaxis += my;
            }
            lastmousetick = MARS_SYS_COMM12;
        }
    }
    return (mousebuttons & 7);
}
Note that this is assuming that IN_MouseButtons() is called first. The lastmousetick simply keeps track of the vertical blank tick when the last time the mouse was accessed. Accessing the mouse too often can cause it to freeze. After getting the mouse value, the deltas are computed from the packet and added to some global variables. Those variables are cleared when you call IN_GetMouseDelta(). You don't HAVE to do it the way shown above... that was the easiest modification of the mouse code in Wolf3D to fit the SEGA mouse. The point is to see how you would compute the mouse deltas from the packet, and how to get the buttons from the packet.

The reason lastmousetick was needed is that Wolf3D uses the mouse in a couple different places, and sometimes polls it in a tight loop. During the game, it doesn't do that. If you wrote your code to guarantee the mouse wasn't polled too often, you wouldn't need to worry about that.

EDIT: IN_MouseButtons() returns the buttons & 7 as Wolf3D only handles 3 buttons by default. Return the buttons & 15 for all four buttons.

Post Reply