Programming the MEGA65 — Chapter 16: The Control Ports

Oct 4, 2026 · Updated Oct 11, 2026 · @Craig Taylor

Introduction

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.

Joysticks

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.

From BASIC

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

From machine language

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.

Multi-Button Controllers

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.

Reading the buttons from BASIC

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:

Reading the buttons from machine language

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.

More Joysticks and Port Options

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:

Software that relies on either should say so clearly, since a standard MEGA65 has neither extra port.

Paddles

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.

Mice

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.

Light Pens

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 Keyboard as an Input Device

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).

The typing event queue

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.

The keyboard matrix registers

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.

From BASIC

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.

Summary

Chapter 17 covers the remaining peripherals.