Oct 4, 2026 · Updated Oct 11, 2026 · @Craig Taylor
The two controller ports on the MEGA65's left side are the same 9-pin ports the C64 had, and they accept the same devices: digital joysticks, paddles, the Commodore 1351 mouse, Amiga mice and light pens. They are wired to CIA 1, as on the C64, so C64 code that reads them works unchanged. The MEGA65 adds BASIC commands for every device, and a much better way to read the keyboard, which this chapter also covers because games often use it as a controller.
A digital joystick has four direction switches and a fire button. Each closes a contact, and a closed contact reads as 0, so the bits are active low. The ports are read through CIA 1:
| Register | Port |
|---|---|
$DC00 |
Port 2 |
$DC01 |
Port 1 |
| Bit | Switch, 0 when pressed |
|---|---|
| 0 | Up |
| 1 | Down |
| 2 | Left |
| 3 | Right |
| 4 | Fire |
The same CIA registers scan the keyboard, which is why holding a joystick in port 1 can produce stray key presses.
JOY decodes a port for you: the low four bits give a direction from 1 (up) clockwise to 8 (up and left), 0 when centred, and each fire button adds its own bit value: 128 for button 1, the trigger, and higher values for the extra buttons described under Multi-Button Controllers below (MEGA65 Book, pp. B-143, B-144). Port 3 returns the merged state of both ports, so a game can accept a joystick in either.
10 DO
20 N = JOY(2)
30 D = N AND 15
40 IF D THEN PRINT "DIRECTION"; D;
50 IF N AND 128 THEN PRINT "FIRE!";
60 PRINT
70 LOOP
Read the CIA register, invert it so pressed switches read as 1, and mask off the five joystick bits:
joy2 lda $dc00
eor #$ff ; pressed = 1
and #$1f ; bits 0-4: up, down, left, right, fire
rts
A routine run with the KERNAL active should read the port when no keys are being scanned, or accept the occasional false reading; a takeover program that does its own keyboard scanning avoids the conflict entirely.
Players with a joystick in the wrong port can swap the ports from the Freezer with J, so a program only needs to support one port to be usable.
The vast majority of Commodore and Atari joysticks have one fire button. Some controllers have more, and because the 9-pin port has only one fire line, the extra buttons are signalled in other ways. BASIC's JOY function recognises two protocols, decoding them in the ROM rather than in hardware (MEGA65 Book, pp. B-143, B-144):
| Protocol | Buttons | How the extra buttons are signalled | Examples |
|---|---|---|---|
| Standard | 1 | The fire line only | Most joysticks |
| Three-button | Up to 3 | Buttons 2 and 3 use the port's two paddle lines, POTX and POTY | C64GS controller, Atari CX78 game pad, many modern pads and adapters |
| 5plusbuttonsJoystick, by crystalct | Up to 5 | Buttons 2 and 3 as above; buttons 4 and 5 reuse the direction signals | Five-button pads, usually with Start and Select |
Controllers differ in how they label the buttons. A third button may be a second action button or one marked Start, and on five-button pads buttons 4 and 5 are typically Start and Select.
JOY reports every button as its own bit, added to the direction:
| Button | Value | Usual label |
|---|---|---|
| 1 | 128 | A, the trigger |
| 2 | 256 | B |
| 3 | 512 | C, or Start |
| 4 | 1024 | Start |
| 5 | 2048 | Select |
AND isolates each button, regardless of the direction and the other buttons:
10 DO
20 N = JOY(2)
30 PRINT "DIR"; N AND 15;
40 IF N AND 128 THEN PRINT " A";
50 IF N AND 256 THEN PRINT " B";
60 IF N AND 512 THEN PRINT " C";
70 IF N AND 1024 THEN PRINT " START";
80 IF N AND 2048 THEN PRINT " SELECT";
90 PRINT
100 LOOP
Three consequences of the protocols are worth designing around:
Button 1 is bit 4 of the port's CIA register, as shown earlier. Buttons 2 and 3 are the port's paddle inputs. On a C64, reading them meant selecting the port through CIA 1 and waiting for the SID to sample the lines; the MEGA65 provides registers that read each port's paddle lines directly, without any SID or CIA setup (MEGA65 Book, p. T-6):
| Register | Name | Contents |
|---|---|---|
$D620 |
POTAX | Port A, paddle X line |
$D621 |
POTAY | Port A, paddle Y line |
$D622 |
POTBX | Port B, paddle X line |
$D623 |
POTBY | Port B, paddle Y line |
A three-button controller connects button 2 to the X line and button
3 to the Y line. A released button leaves the line unconnected, so it
reads as a high value near $FF, as an empty paddle input
does; a pressed button pulls the line up and it reads as a low value.
Treating anything below $80 as pressed gives a reliable
result:
; return buttons 2 and 3 of the pad on port A in bits 1 and 2 of A
btn23 lda #0
ldx $d620 ; POTAX: button 2
cpx #$80
bcs nob2 ; high: not pressed
ora #%00000010
nob2 ldx $d621 ; POTAY: button 3
cpx #$80
bcs nob3
ora #%00000100
nob3 rts
The Book names the ports A and B; press a button on a pad in each port and watch the registers to confirm which physical port each pair belongs to. Buttons 4 and 5 of the 5plusbuttonsJoystick protocol are encoded in the direction signals; JOY decodes them in BASIC, and machine language that needs them should follow the protocol's own specification.
The MEGA65 has two joystick ports and no user port (MEGA65 Book,
setting up, connectors), so the C64's four-player adapters, which plug
into the user port, cannot be used.
Games for more than two players need another input, such as the keyboard
matrix described below, or players sharing a port in turn. The MEGA65
does provide some extra control over the two ports it has, in register
$D612 (MEGA65 Book, pp. T-4, T-5):
| Bit | Name | Effect when set |
|---|---|---|
| 7 | LJOYB | Rotates joystick B's directions by 180 degrees, for left-handed use |
| 6 | LJOYA | The same for joystick A |
| 5 | JOYSWAP | Exchanges joystick ports 1 and 2 |
JOYSWAP is the hardware behind the Freezer's J command. The swap and
rotation are applied to the joystick switch lines, so they affect the
CIA registers and JOY's directions and fire button with no special code.
They do not affect the paddle lines: POT, mice, and buttons 2 and 3 of a
multi-button controller still follow the physical ports (mega65-core,
keymapper.vhdl).
Two hardware routes go further, and both are for experimenters rather than for software meant to run on every MEGA65:
$D625-$D628, with separate data direction
registers.Software that relies on either should say so clearly, since a standard MEGA65 has neither extra port.
A paddle is a dial connected to a variable resistor, and each port takes a pair. The SID chip measures the resistance and reports it as a number from 0 to 255. Each paddle also has a fire button, read like a joystick switch.
POT reads paddles 1 to 4: the low byte is the position, and a value above 255 means the button is pressed (MEGA65 Book, p. B-197):
10 X = POT(1)
20 B = X > 255 : REM -1 WHILE THE BUTTON IS PRESSED
30 V = X AND 255 : REM POSITION, 0 TO 255
C64 software reads the two values from SID registers
$D419 and $D41A, after selecting a port
through bits 6 and 7 of CIA 1's port A. The MEGA65 adds four registers
that read all four paddles directly, with no SID or CIA setup; they need
the MEGA65 I/O personality:
| Register | Paddle |
|---|---|
$D620 |
Port 1, X |
$D621 |
Port 1, Y |
$D622 |
Port 2, X |
$D623 |
Port 2, Y |
paddle lda $d620 ; port 1, paddle X: 0-255
sta pos
lda $dc01 ; CIA 1 port B holds port 1's switches
and #%00000100 ; paddle X's button is the joystick left line
rts ; Z set while the button is pressed
As with a joystick, a button reads 0 while pressed. Paddle Y's button
is on the right line, bit 3, and port 2's buttons are on
$DC00.
The MEGA65 accepts the Commodore 1351 mouse, Amiga mice, and modern USB mice through adapters such as the mouSTer. The configuration utility's Input page sets the mouse type for each port: "normal" means the 1351. An Amiga mouse in a port set to normal can interfere with the keyboard (MEGA65 Book, p. 4-4).
BASIC 65 includes a complete mouse driver. MOUSE ON starts it, with a sprite as the pointer; RMOUSE reads the position and buttons; MOUSE OFF stops it:
10 MOUSE ON, 1 : REM MOUSE IN PORT 1, SPRITE 0 AS POINTER
20 DO
30 RMOUSE X, Y, B
40 IF B THEN PRINT "CLICK AT"; X; Y
50 LOOP
Sprite 0 holds a mouse-pointer image after start-up; another sprite needs pointer data loaded first (MEGA65 Book, MOUSE). The driver runs in the KERNAL's raster interrupt, so the pointer moves smoothly while the program does other work (MEGA65 ROM source, editor_irq). RMOUSE returns -1 only when the driver is off; it cannot tell whether a mouse is actually plugged in. Press RUN/STOP to end the loop, then MOUSE OFF.
A light pen detects the moment the display's beam passes under its
tip, and the video chip latches the position. LPEN(0) and LPEN(1) return
the X and Y positions, and the Book describes a light pen on port 1 with
a CRT display (MEGA65 Book, p. B-163). In machine language the latched
position is in $D013 (X) and $D014 (Y), and
bit 3 of $D019 flags a new reading, as on the C64.
In the current core, however, the latch is set only by the touch
input of the on-screen keyboard, not by a pen on the control port (mega65-core,
viciv.vhdl, September 2026). A program should not count on
a light pen working, and few MEGA65 owners have the CRT one would
need.
The C64 read its keyboard by scanning a matrix through CIA 1, which is slow, misses some combinations of keys, and produces "ghost" keys when certain keys are held together. The MEGA65 still supports CIA scanning for old software, but adds two better methods (MEGA65 Book, Appendix F).
For text input, the hardware keeps a queue of typing events, already translated into characters:
| Register | Name | Contents |
|---|---|---|
$D610 |
ASCIIKEY | The next event as ASCII; $00 if the queue is empty,
$FF if it has no ASCII form |
$D619 |
PETSCIIKEY | The next event as PETSCII; $FF if the queue is empty or
the event has no PETSCII form (bit 7 of $D60A tells these
apart) (mega65-core PR #720) |
$D60A bits 0-6 |
MODKEY | Modifier keys held during the event |
$D60A bit 7 |
KEYQUEUE | 1 while the queue is not empty |
Writing any value to $D610 or $D619 removes
the event from the queue. ASCIIKEY gives access to characters PETSCII
lacks, such as {, | and _, and to
accented Latin-1 characters typed with the MEGA key (MEGA65 Book, pp.
F-3, F-4).
wait lda $d610 ; ASCIIKEY
beq wait ; $00: nothing typed yet
sta $d610 ; remove it from the queue
rts ; A holds the character
The KERNAL's GETIN and BASIC's GET and GETKEY read the same queue, so a program should use one method or the other, not both.
For games, which need to know which keys are held down at once, the MEGA65 exposes the C65 keyboard matrix directly. It has nine columns of eight rows (MEGA65 Book, p. F-14):
| Register | Use |
|---|---|
$D614 |
Write a column number, 0 to 8 |
$D613 |
Read the column: a row's bit is 0 while its key is pressed |
$D611 bits 0-6 |
Modifier keys: a bit is 1 while the key is pressed |
Unlike CIA scanning, these registers report any combination of keys without ghosting, and they do not disturb the joystick ports. The Book's keyboard appendix gives the key at each row and column. The space bar, for example, is in column 7, row 4:
space lda #7 ; select column 7
sta $d614
lda $d613 ; read its eight rows
and #%00010000 ; row 4: the space bar
rts ; Z set while space is held
A game can poll several columns this way every frame, and react to any combination of keys held at once.
GET and GETKEY read the typing queue. For held keys, a BASIC program can read the matrix registers with POKE and PEEK, though the keyboard is usually simpler to treat as a source of single key presses in BASIC.
$DC00 (port 2) and $DC01 (port 1), active low,
bits 0-4. Three-button and five-button controllers add buttons 2 to 5,
which JOY reports as 256, 512, 1024 and 2048; machine language reads
buttons 2 and 3 from the paddle registers $D620-$D623.$D620-$D623, or SID $D419 and
$D41A as on the C64.$D013/$D014,
though the current core latches only touch input.$D610,
$D619 and $D60A gives characters; the matrix
registers at $D614, $D613 and
$D611 give held keys without ghosting.Chapter 17 covers the remaining peripherals.