Below, "PR" is the Peripheral Data register at $DC00
and $DC01
for
I/O ports A and B and "DDR" is the Data Direction Register at $DC02
and $DC03
for ports A and B.
To deal with this well it's important to understand the schematic on
page 12 of the service manual, and the keyboard matrix. (The
keyboard matrix diagram in the service manual is incomplete.)
To get the information we need from the schematic, you don't need to
understand anything special about electronics beyond switches and
wires being connected together. We see below, for example, that pin
PA1 on the CIA, COL1 on the keyboard connector, and JOYB1 on control
port 2 are all connected together; all of these are part of the "COL1
line."

The joystick and keyboard wiring itself is not shown on the above
diagram. Joysticks connected to the control ports have normally-open
switches that short to ground when closed; e.g., pushing up on a
joystick connected to port 1 would short pin 1 to pin 8 on the
joystick connector, thus bringing the level of the ROW0 low.
The keyboard keys connect COL0-7 lines to ROW0-7 lines as described in
more detail below.
The core concept is that you have a set of sixteen lines, COL0-7 on
CIA pins PA0-7 and ROW0-7 on CIA pins PB0-7, each of which will have a
high or low level you can read.
The lines default to high when nothing connects them to ground; this
is done by pull-ups in the CIA. But if any of these lines are
connected to ground, via a keyboard or joystick switch or a setting both
DDR=1 and PR=0 in the CIA itself, the line will be at low level,
always overriding both the default pull-up to high and any CIA setting
of DDR=1,PR=1 for that pin.
There may be multiple connections to ground, making this a "wired-OR"
system: any one of these connections, if made to ground, will force
the line low regardless of how any other connection is set. In other
words, it's not possible for the line to be high if anything on that
line wants to bring it low.
There are three basic ways a line can be brought low:
- A joystick switch, connected directly to ground, is closed.
- A keyboard switch is closed and one of its two lines (COLn or
ROWn) is low, bringing the other line low.
- The CIA has been programmed to bring the line low by setting its
bit in DDR to
1
and setting its bit in the PR to 0
. Any
other setting will not drive the line low (at least not by the
CIA). In particular, setting both DDR and PR to 1
will not
necessarily drive the line high; it will just leave it at whatever
value is a consequence of other settings (high by default, or low
if anything else connects it to ground).
The wiring for the joysticks is the simplest: each switch (left,
right, up, down and fire button), when closed, brings low the line to
which it's connected (COL0-4 for joystick port 2 and ROW0-4 for
joystick port 1.)
The keyboard is slightly more complex. With the exception of the
RESTORE key (which is handled completely separately by means other
than the CIA), each key connects one COL line and one ROW line
together when pressed, as in a matrix switch. Here's the matrix
diagram from my link above:

Thus, pressing a key will do do nothing at all unless something
(hopefully you, by programming the CIA) has brought at least one COLn
or ROWn line low, in which case a read will show corresponding ROWn or
COLn lines as low for any pressed keys connecting the two. This
includes the situation described in your link Scanning the Keyboard
the correct and non-KERNAL way where the path goes through
multiple keys, e.g., grounding COL2 and pressing A and S to give a
path COL2→A
→ROW1→S
→COL5 that brings COL5 low.)
When scanning the keyboard, there's no particular reason as far as I
know to choose to force COLn lines low and read the ROWn lines or vice
versa. In fact, doing it both ways might even help you to do more
reliable reads. (I provide a example of this below.)
Since you've got custom code to read the keyboard anyway, you should
combine it with the code to read the joystick. This will allow you to
reliably read both to the degree the hardware design allows, let you
use your understanding of the above to design your controls to avoid
conflicts, and also probably run a bit faster.
Since presumably no code other than your own will be touching the CIA
registers, you can set up DDRA and DDRB once when your game starts and
never touch them again. I'd just set them both to $FF
when you
initialize your game so that you never need write them again. As
explained above, so long as PRA and PRB are are also set all to $FF
,
this will have the same effect as if DDRA and DDRB are both $00
; the
CIA will not force any lines low.
When you scan, first write $FF
to PRA and PRB and then read them.
This will tell you both which joystick switches are closed for each
joystick and tell you which keyboard columns and/or rows you will
not be able to scan.
What you do next depends on whether you're allowing 1 or 2 joysticks,
what keyboard keys you're using, and how sophisticated you want to
get. The easiest situation is if you ignore the keyboard when either
player is pressing the joystick (i.e., player or players have to let
go the joystick(s) to enter a keyboard command), which means at this
point you're done.
But let's say you want to use both at the same time because you have a
two-player game. In this case you could put player one using a
joystick on control port 2 and have player two use the keyboard.
Joystick 1 is connected to COL0-4, so if you limit player 2 to keys on
COL5-7 there can never be interference between the two. You could for
example use RUN/STOP
/C=
for up/down, :
/=
for left/right, and
spacebar for fire. In this case your keyboard scan would write PRA to
bring low in turn:
- COL7, checking for ROW7 (
RUN/STOP
), ROW5 (C=
) and ROW4 (spacebar);
- COL6, checking for
=
; then
- COL5, checking for
:
.
Another variation would be to use the keyboard to provide extra
controls in a two-player, two-joystick game. If you use left and right
SHIFT
as a second action (hyperspace?) for each player, you'd first
scan the joysticks by writing $FF
to PRA and PRB as above and
reading PRA (joystick 2) and PRB (joystick 1), as above. Then you
bring low in turn:
- COL1, checking ROW7 to see if left
SHIFT
is pressed; and
- ROW4, checking COL6 to see if right
SHIFT
is pressed.
Actually, you don't even need to do the write to bring low COL1 if
joystick 2 is currently pushed down, or ROW4 if joystick 1's fire
button is currently pressed, but the logic for those special cases is
probably longer and slower than just doing the write. There might be
some situation in which this idea could be used, though, such as
SPACE
modifying the action of the fire button on joystick 1.
There are endless variations of this; you just need to use the matrix
information to figure out what's possible and which switches/keys
interfere with each other.