Skip to content

AppleMouse

The AppleMouse card is fairly complex considering what it does. It's got an onboard microprocessor (6805), a 6520 chip (according to one thing I read). 6520 is like 6522 without timers.

The programming references for it specify use of a bunch of standardized firmware vectors. It's got a 2K ROM for C800-CFFF.

So there are two options: ParaVirtual, or emulate the hardware.

https://github.com/freitz85/AppleIIMouse/blob/master/AppleMouse.txt

ClampMouseX ->  W 4 Bytes   LLX, LHX, HLX, HHX
ClampMouseY ->  W 4 Bytes   LLY, LHY, HLY, HHY
SetMouse    ->  W 1 Byte    MODE
ServeMouse  ->  R 1 Byte    STATUS
ReadMouse   ->  R 6 Bytes   LX, LY, HX, HY, STATUS, MODE
ClearMouse  ->  W 4 Bytes   LX, LY, HX, HY
PosMouse    ->  W 4 Bytes   LX, LY, HX, HY
HomeMouse   ->  R 4 Bytes   LLX, HLX, LLY, HLY
                W 4 Bytes   LX, LY, HX, HY

A0-A3:

Write:
0x00    Position:   LX, HX
0x01                LY, HY
0x02    BoundaryX:  LLX, LHX
0x03                HLX, HHX
0x04    BoundaryY:  LLY, LHY
0x05                HLY, HHY
0x06    State:      MODE
0x07    Set to Input

Read:
0x08    Position:   LX, HX
0x09                LY, HY
0x0a    BoundaryX:  LLX, LHX
0x0b                HLX, HHX
0x0c    BoundaryY:  LLY, LHY
0x0d                HLY, HHY
0x0e    State:      STATUS
0x0f    Reset to default

These are 6805 microcontroller register indexes.

  • PA:

PA0-7 on the 6520 is connected to the microcontroller data lines D0-7.

  • PB: likely set for output direction

PB4-7 are connected to the microcontroller PC0-3 lines - Port C I/O lines. I suspect these are the register selects. PB1-3 are connected to PB1-3 but also pulldowns, and, also A8-A10 on the EPROM (hmm). So this is apparently used to bankswitch the ROM.

PB0 is connected to "sync latch" from some clock related chip.

CB1 is tied to 6805:T to +5 with a pull-up. T is timer, unused, so in short this is just always '1'. But also tied to 6805:/INT pin 2

CB2, CA1, CA2 are NC/unused.

A0-1 are connected to 6520 RS0-1.

IRQ connects to PB6 on 6805.

R/W to the 6805 is determined by the address. I.e. address 0-7 is reads, 8-F is write.

On a read, put the value from the MC into PIB. On a write, we use the value in ORB.

Here's a disassembly of the ROM:

https://github.com/freitz85/AppleIIMouse/blob/master/mouse%20rom.s

This is a project which emulates the Apple Mouse II card pretty closely:

https://www.applefritter.com/content/a2usb-apple-ii-usb-mouse-interface-card-emulation

ok I'm going to guess the following:

BoundaryX provides a range from L to H - low value to high value. And a similar range for Y low to high. This clamps the mouse Position to be inside that range. Simple enough. What we need is the State: MODE and STATUS values.

The position X and Y can range from -32768 to 32767 (signed 16 bit integer). "Are normally clamped to range $0 to $3FF (0 to 1023 decimal).

ah ok these are in the manual. The Mode byte:

Bit Function
0 Turn mouse on
1 Enable interrupts with mouse movement
2 Enable interrupts when button pushed
3 Enable interrupts every screen refresh
4-7 Reserved

if you write value of $01 to mode, it's "passive mode" - i.e., no interrupts.

Status

Bit Function
7 Button is down
6 Button was down at last reading
5 x or Y changed since last reading
4 Reserved
3 interrupt caused by screen refresh
2 interrupt caused by button press
1 interrupt caused by mouse movement
0 Reserved

Oh, somewhere we need a register for a mouse click? What is SERVEMOUSE?

if you use an interrupt mode, the interrupt handler must call SERVEMOUSE as part of that - it determines whether interrupt was caused by mouse so we can call READMOUSE if it was.

So what clears the interrupts? Just reading the STATUS byte perhaps?

6805

/RESET - system bus reset /INT - tied to pullup along with 6520:CB1 (which seems to be an input only). Not likely this is used anywhere. But make available to read.

6520

6520 has these registers: | Register | Function | |-----|----------| | CRA | Control Register A | | DIR | Data Input Register | | DDRA | Data Direction Register A | | DDRB | Data Direction Register B | | PIBA | Peripheral Interface Buffer A | | PIBB | Peripheral Interface Buffer B | | DDRB | Data Direction Register B | | ISCB | Interrupt Status Control B | | CRB | Control Register B | | ORA | Peripheral Output Register A | | ORB | Peripheral Output Register B |

The two RS lines (RS0/1) and CRA/CRB select various 6520 registers to be accessed. RS0 = A0, RS1 = A1.

There are pins RS0/1 on the 6520 - Register Select. There are only two of these, so there are only four register locations.

The 6520 IRQ lines are not used. IRQ is connected to the MC6805.

CRA and CRB

7 6 5 4 3 2 1 0
CRA IRQA1 IRQA2 CA2 Control DDRA Access CA1 Control
CRB IRQB1 IRQB2 CB2 Control DDRB Access CB1 Control

When Bit 2 = 0 (see below), access DDR. When Bit 2 = 1, access PIB.

CA1/CB1 are interrupt input pins only. CA1 is NC. CB1 is tied to pullup so it just always high. Should always read as 1. CA2 is NC, CB2 is NC.

Cx2 control: bit 5 = 1, output mode "ca2 can operate independently to generate a simple pulse each time the microprocessor reads the data on the peripheral port". Since Cx2 is NC we can disregard the values of the CA2 and CB2 control bits. CR bit 1 (CA1 high bit) is 0 for negative transition, 1 for positive transition. These values are unused.

This is covered on Page 2-29 of the 6520 data sheet.

Register Access

A2 Bus Addr Register Address RS1 RS0 CRA (bit 2) CRB (Bit 2) R/W=H R/W = L
C080 0 L L 1 - Read PIBA Write ORA
C080 0 L L 0 - Read DDRA Write DDRA
C081 1 L H - - Read CRA Write CRA
C082 2 H L - 1 Read PIBB Write ORB
C082 2 H L - 0 Read DDRB Write DDRB
C083 3 H H - - Read CRB Write CRB

(But also addresses C084-7, C088-B, C08C-F)

Paravirtualization Approach

Apple2TS uses a paravirtual approach for its mouse implementation. Which is interesting, however, the device is still fairly complex, with interrupts, multiple registers, etc etc.

There probably should not be any software that directly accesses the hardware as Apple started pushing devs hard on this at that time.

Hardware Emulation Approach

Practically speaking we only need to emulate the 6520 interface.

Screen Holes

$0478 + slot Low byte of absolute X position
$04F8 + slot Low byte of absolute Y position
$0578 + slot High byte of absolute X position
$05F8 + slot High byte of absolute Y position
$0678 + slot Reserved and used by the firmware
$06F8 + slot Reserved and used by the firmware
$0778 + slot Button 0/1 interrupt status byte
$07F8 + slot Mode byte

ROM Mapping

the first 256 bytes of the ROM seem to be $Cn00-$CnFF. And perhaps then the whole 2K ROM is also at $C800-$C8FF. However it is unclear how the $C800 space is mapped in. The IOSTROBE line is connected into a small PAL chip, and this may have an output on pin 17 that goes to PB0. Alright, so PB0 is mapped as an input (in the source code, for "sync latch" whatever that is). Putting $3E into PB should then result in PB1-3 being E, and setting address bits of the ROM A8-A10 to 111. The low byte of PB is what ROM page to map in.

So, I don't think that it does. I think ORB is used to set the firmware bank that appears in $Cn00. There are no absolute references JSR/JMP inside $C800 in the firmware. And, in every bank, at $7B there is a "PLA" - i.e, here is the code in $Cn00:

C85B   B9 83 C0   LC85B     LDA $C083,Y
C85E   29 FB                AND #$FB
C860   99 83 C0             STA $C083,Y
C863   A9 3E                LDA #$3E
C865   99 82 C0             STA $C082,Y
C868   B9 83 C0             LDA $C083,Y
C86B   09 04                ORA #$04
C86D   99 83 C0             STA $C083,Y
C870   B9 82 C0   LC870     LDA $C082,Y
C873   29 C1                AND #$C1
C875   1D B8 05             ORA $05B8,X
C878   99 82 C0             STA $C082,Y
C87B   68                   PLA

There is that same PLA in almost every bank. This is because $C878 is setting the bank number to 111 (7) which means ROM $700-$7FF. So this falls through to this:

CF7B   68                   PLA
CF7C   D0 82                BNE LCF00
CF7E   A9 00      LCF7E     LDA #$00
CF80   9D B8 05             STA $05B8,X
CF83   48                   PHA

This is really freakin crazy. They do all this just to save some address decode logic? Or was there some other purpose?

BazMouse

ok, I think it may be significantly simpler to write my own mouse card setup.

Screen Hole Description
$478 + n Low byte of X position
$4F8 + n Low byte of Y position
$578 + n High byte of X position
$5F8 + n High byte of Y position
$678 + n Reserved
$6F8 + n Reserved
$778 + n Button and interrupt status
$7F8 + n Current mode
Address Name
C0s0 RW
C0s1 RW
C0s2 RW
C0s3 RW
C0s4 RW
C0s5 RW
C0s6 RW
C0s7 RW
C0s8 RW
C0s9 RW
C0sA RW
C0sB RW
C0sE R
C0sF RW

Mode

Bit Function
0 Turn mouse on
1 Enable interrupts on mouse movement
2 Enable interrupts when button pressed
3 enable interrupts every screen refresh
4-7 Reserved

Status

Bit Function
Bit 7 Button is down
6 Button was down at last reading
5 X or Y changed since last reading
4 Reserved
3 Interrupt caused by screen refresh
2 Interrupt caused by button press
1 Interrupt caused by mouse movement
0 Reserved

The application program is responsible for setting up the interrupt vector and handler routines.

Mouse API Routines

SETMOUSE

Input: A = mode value X = $Cn Y = $n0

Return:

C = 1: if mode byte is illegal (greater than $0F) C = 0: everything fine. No change to registers or screen holes.

SERVEMOUSE

If an interrupt was caused by mouse, SERVEMOUSE updates the status byte ($778+n) to show which event caused the interrupt.

Return: C = 0: interrupt caused by mouse C = 1: interrupt not caused by mouse.

READMOUSE

Transfers current mouse data to screen holes. Sets bits 1,2,3 in status byte to 0.

CLEARMOUSE

Sets X and Y position values to 0, both in screen holes and on card. Button and interrupt status remains unchanged.

POSMOUSE

sets position registers on peripheral card to the values it finds in the screen holes.

CLAMPMOUSE

Input: A = 0: change X coordinate limits A = 1: change Y coordinate limits

New boundaries are read from Apple main memory: $478 - low byte of lower bound $4F8 - low byte of higher bound $578 - high byte of lower bound $5F8 - high byte of higher bound

destroys contents of mouse's X and Y position screen holes. Program must follow CLAMP with a READMOUSE.

HOMEMOUSE

Sets mouse position registers on the peripheral card to the lower boundaries. Does not update the screen holes. To do that, you should follow with READMOUSE.

INITMOUSE

sets internal default values for mouse subsystem and synchronizes with monitor's vertical blanking cycle.

Call jump table

Offset Function
$Cn12 Low byte of SETMOUSE entry point address
$Cn13 Low byte of SERVEMOUSE entry point address
$Cn14 Low byte of READMOUSE entry point address
$Cn15 Low byte of CLEARMOUSE entry point address
$Cn16 Low byte of POSMOUSE entry point address
$Cn17 Low byte of CLAMPMOUSE entry point address
$Cn18 Low byte of HOMEMOUSE entry point address
$Cn19 Low byte of INITMOUSE entry point address

Translation of Mouse Motion

The virtual Apple will have its own clamping. This presents two options: 1) SetWindowRelativeMouseMode will constrain the mouse to the interior of the window; when we're using the mouse in the gameport code, we translate and scale mouse-in-window coordinates to 0-255. This means there is a different scale vertically and horizontally. I don't think that's what we want. 2) Have the above mode, BUT, use Mouse movement events to update the virtual mouse x and y positions.

I think the latter approach is better - x and y will have the same sensitivity this way, and, we're not having to deal with translating and scaling. It also deals better with setting the mouse position, and we won't have to do a SDLSetMousePos call.

SDL_MouseMotionEvent is the ticket.

I have an odd conflict: A2Desktop can use both mouse and joystick and gets confused. So when the Mouse is active (turned on), disable Mouse-Joystick emulation.

Interrupts

there are three interrupts: vbl, on mouse motion, on button click. "upon detecting an interrupt event, the mouse subsystem sends an IRQ to the 6502 at the end of the current monitor screen writing cycle". So, it only sends IRQ when we're in VBL.

OK, whenever we initmouse, this synchronizes with the vbl. So we can calculate where the vbl should be and set and event timer for that cycle. The handler for that event will check whether the mouse moved, button pressed, or .. i.e. we should set flags "this event occurred". when the handler is called, we (may) push these into the IRQ flags then OR them and set (or clear) the slot IRQ. then clear the underlying flags?

ok. So we can grab the cycle counter whenever the mouse is enabled. grab this from the video routines. That's the simplest way, and will work on ii plus mode or //e mode.

This is what Shufflepuck author learned writing it:

https://www.colino.net/wordpress/en/archives/2025/05/08/yes-the-apple-ii-mousecard-irq-is-synced-to-the-vbl/

(for initial test, we don't care, just pick current cycle plus 17,030)

Compatibility

A2Desktop

working w/o interrupts. However, when the gamepad is not connected, and gamecontroller uses mouse to emulate joystick, there is a conflict in that some mouse clicks and motion cause joystick which interferes with reading from the mouse card. We should disable mouse-based gamecontroller when mouse card mode is Active.

Shufflepuck

[x] starts to blank screen?
[ ] The mouse is overly sensitive

plays a short bit of music hangs on blank screen in a tight loop waiting for $B003 to become non-zero.

likely waiting on mouse interrupts. maybe even vbl.

the blank screen issue was unrelated to mouse, it was not handling the //e quasi video mode with 80col off but hires on doing no-shift mode.

DazzleDraw

also likely waiting on mouse interrupts. specifically, DD wants VBL interrupt. So that's easy to implement.

status byte toggles between 0x60 -> 0xA0 when I click mouse. If I spastically click mouse many times it may get one click every so often through.

Bugs

[ ] When I click and drag (like choosing a menu), only the X or the Y coordinate can change, not both. That is weird. not reproducing this today with debugging off

[ ] when using apple-W to close, well it actually closes the emulator window.

Cmd-W is being acted on by the MacOS and closing the window before I ever have a chance to do anything with it.

[ ] in DD, clicking in-window does not cause mouse to be captured

videosystem was returning true on the mouse capture section - changed to -false- because we also still want these clicks passed through to whatever there is underneath looking for these events.

in DD about 20% to 40% of vertical area, the cursor gets weird. So VBL is in the wrong place.

Other Implementations

AppleWin

Emulates the 6521 interface (labels it a 6821), so it's likely simulating the AppleMouseII fairly directly and using its firmware.