Mouse

One of the most well-known non-controller peripherals is the mouse. While not supported by many games, if you're making one that needs accurate aiming or pointing (especially if fast paced) then adding support for it can be great, especially since the mouse is reasonably easy to acquire.

The US version has two more buttons, while the Japanese and European versions can be used like a handheld trackball (otherwise, they work the same way).

Setting up

If you try to detect it using the peripheral ID, the mouse has an ID of 3 (%0011).

Setting up the mouse is similar to setting up the controllers, except that instead of writing $40 to the control and data ports, you write $60.

    ; Assuming 2P port
    move.b  #$60, (IoCtrl2)
    move.b  #$60, (IoData2)

The addresses come from the I/O ports article.

Reading the mouse

First, read back from the data port and check if bits 3-0 are 0000. If they aren't, assume no mouse is here.

Now, every time you read the mouse (once per frame, like the controllers), it returns the data in a packet, which consists of nine nibbles. The idea goes like this (all this on the data port):

  1. Write $20, wait until bit 4 = 1, read nibble from bits 3-0
  2. Write $00, wait until bit 4 = 0, read nibble from bits 3-0
  3. Write $20, wait until bit 4 = 1, read nibble from bits 3-0
  4. Write $00, wait until bit 4 = 0, read nibble from bits 3-0
  5. Write $20, wait until bit 4 = 1, read nibble from bits 3-0
  6. Write $00, wait until bit 4 = 0, read nibble from bits 3-0
  7. Write $20, wait until bit 4 = 1, read nibble from bits 3-0
  8. Write $00, wait until bit 4 = 0, read nibble from bits 3-0
  9. Write $20, wait until bit 4 = 1, read nibble from bits 3-0
  10. Write $60 to finish

You may want to put a timeout, i.e. if it takes too long for bit 4 to set to the correct value, bail out. This can happen if the mouse gets unplugged in the middle of reading (your game would hang up without the timeout!).

Some sample code (it reads the nibbles into a buffer):

; Reads a mouse packet
; d0.w = 0 on success
;       -1 on failure

ReadMouse:
    ; I/O data port to read from
    ; Use IoData2 for player 2
    lea     (IoData1), a0
    
    ; Buffer to store the nibbles
    ; (we use up one byte per nibble)
    lea     (MouseBuffer), a1
    
    ; Look-up table, see below
    lea     (@Table), a2
    
    ; Now loop through all nibbles
    ; (the -1 is because of DBF)
    moveq   #9-1, d0
@Loop:
    
    ; Tell mouse to send next nibble
    ; and wait for it to be ready (if
    ; it takes too long we bail out)
    move.b  (a2)+, (a0)
    move.w  #$FF, d7
@Wait:
    moveq   #$10, d6    ; Mask out bit 4 and
    and.b   (a0), d6    ; check if it matches
    cmp.b   (a2), d6    ; what we want
    beq.s   @GotIt
    
    dbf     d7, @Wait   ; Keep waiting if not,
    bra     @Error      ; or throw error if we
                        ; waited way too long
    
    ; Got the nibble, store it into a
    ; buffer and move onto the next one
@GotIt:
    moveq   #$0F, d6    ; Mask out the nibble
    and.b   (a0), d6    ; and store it into
    move.b  d6, (a1)+   ; the buffer
    
    lea     1(a2), a2   ; Advance look-up table
    dbf     d0, @Loop   ; Keep looping
    
    moveq   #0, d0      ; Success!
    rts                 ; End of subroutine
    
@Error:
    moveq   #-1, d0     ; Failure...
    rts                 ; End of subroutine

; Look-up table used in the loop above
; 1st byte is what to write, 2nd byte is
; what to wait for (in bit 4)
@Table:
    dc.b    $20,$10   ; 1st nibble
    dc.b    $00,$00   ; 2nd nibble
    dc.b    $20,$10   ; 3rd nibble
    dc.b    $00,$00   ; 4th nibble
    dc.b    $20,$10   ; 5th nibble
    dc.b    $00,$00   ; 6th nibble
    dc.b    $20,$10   ; 7th nibble
    dc.b    $00,$00   ; 8th nibble
    dc.b    $20,$10   ; 9th nibble

OK, now onto the nibbles:

Mouse packet format
Bit 3 Bit 2 Bit 1 Bit 0
1st nibble ($20) 1 0 1 1
2nd nibble ($00) 1 1 1 1
3rd nibble ($20) 1 1 1 1
4th nibble ($00) Yo Xo Ys Xs
5th nibble ($20) C M R L
6th nibble ($00) X7 X6 X5 X4
7th nibble ($20) X3 X2 X1 X0
8th nibble ($00) Y7 Y6 Y5 Y4
9th nibble ($20) Y3 Y2 Y1 Y0

First two things to watch out:

The mouse motion is given by X7-X0 and Y7-Y0. The Xs and Ys flags are the sign and act as if they were an extra bit: they indicate if you should sign extend with zeroes or ones. Make sure to not sign extend 0 or you'll get -256 instead. X increments to the right, Y increments upwards (you may want to flip its sign).

Sample code to do the sign extension:

; d0.w = X coordinate (6th-7th nibbles)
; d1.w = Y coordinate (8th-9th nibbles)
; d2.b = flags (4th nibble)

    ; If you didn't already, make sure
    ; the coordinates are word-sized
    and.w   #$FF, d0
    and.w   #$FF, d1
    
    ; The code below uses a double NEG
    ; to ensure only values that aren't
    ; 0 are sign extended (when it's 0
    ; then it leaves it as 0)
    
    ; Check if X is negative
    btst    #0, d2
    beq.s   @PositiveX
    neg.b   d0
    neg.w   d0
@PositiveX:
    
    ; Check if Y is negative
    btst    #1, d2
    beq.s   @PositiveY
    neg.b   d1
    neg.w   d1
@PositiveY:

The bits in the 5th nibble tell you the state of each button (1 = pressed, 0 = released):

Moving the cursor around

The mouse tells you how much it moved, but it has no idea what's going on screen. If your game is using the mouse to move a cursor then it's up to you to keep track of it.

Moving the cursor is a matter of adding the motion to its current position:

cursor_x = cursor_x + mouse_x
cursor_y = cursor_y + mouse_y

After that you also have to take care that the cursor doesn't go beyond the screen boundaries (or whatever rectangle you want to restrict it to). After applying the motion make sure to limit it to the boundaries:

if cursor_x < min_x then cursor_x = min_x
if cursor_x > max_x then cursor_x = max_x
if cursor_y < min_y then cursor_y = min_y
if cursor_y > max_y then cursor_y = max_y

Click and double click

Detecting a "click" is pretty much the same thing as checking when a button just goes down in a controller, but with the mouse buttons.

To check for a double click, you need to set up a timer when the first click happens. If another click comes in within certain threshold (e.g. within 30 frames or so), you can treat it as a double click. So something like this:

if clicked == true
   if click_timer > 0
      double_clicked = true
      click_timer = 0
   else
      double_clicked = false
      click_timer = click_threshold
   end
else
   if click_timer > 0
      click_timer = click_timer - 1
   end
end

Further improvements:

Left-handed mouse

The mouse has no idea of whether it's right-handed or left-handed, this is up to you. Generally, this means swapping the bits for the left and right buttons while configured for left-handed use. How you do this is up to you.

If you're writing in assembly there's a handy trick with the extend flag:

    roxr.b  #1, d0  ; Push L into extend
    ror.b   #1, d0  ; Push R into bit 7
    roxl.b  #2, d0  ; Pull both back

Mega Mouse vs Sega Mouse

There are two variants of the mouse: Mega Mouse is the American version, while Sega Mouse is the Japanese and European version (yes, they got the names backwards for the mouse). While the protocol is the same, there are some important differences regarding the buttons: