2

I'm implementing non-KERNAL joystick and keyboard controls for my Commodore 64 game.

I used to use the KERNAL SCNKEY routine earlier to read keyboard and it worked well with my joystick port 2 code. However, I had to bank out ROM to implement stable raster IRQs and now I'm reading the keyboard matrix directly using $dc00/$dc01 (code copied from http://codebase64.org/doku.php?id=base:scanning_the_keyboard_the_correct_and_non_kernal_way)

However, the keyboard routine needs to write a row/column select bit into $dc00 when reading the keyboard state. This seems to mess up joystick port 2 state (keyboard state leaks into joystick port reads).

I noticed that if I save and restore $dc00 contents before/after calling the keyboard routine, joystick state works fine.

Is it OK to do something like

lda $dc00
sta tmp
jsr Keyboard
lda tmp
sta $dc00

to preserve joystick state or is there some other way to do this? This worked fine on VICE but I haven't tried on real C64 HW yet.

I did try to read the KERNAL SCNKEY source code to see if they're doing a $dc00 save/restore and didn't find an obvious place where they'd be doing this register state save/restore.

(Cross posting from https://stackoverflow.com/questions/50229204/reading-both-keyboard-and-joystick-with-non-kernal-code-on-c64)

EDIT:

JeremyP's answer is right about DDR regs. This code seems to work with joystick on port 2 and the keyboard code from http://codebase64.org/doku.php?id=base:scanning_the_keyboard_the_correct_and_non_kernal_way:

read_keyboard: {
    jsr Keyboard
    ldx #0
    stx $dc02 // put joystick port 2 register $dc00 to read mode
    rts 
}
  • 1
    Not commenting on what it might do to the hardware, but from a programming stylistic pov, you should save and restore the state inide the keyboard routine. – JeremyP May 8 '18 at 9:24
  • Sure and that’s more or less how my real code looks like. The example was just to illustrate what I do on the higher level. – Nurpax May 8 '18 at 10:25
4

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

C64 joystick/keyboard schematic

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:

  1. A joystick switch, connected directly to ground, is closed.
  2. A keyboard switch is closed and one of its two lines (COLn or ROWn) is low, bringing the other line low.
  3. 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:

Comodore 64 keyboard matrix

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.

2

Having read the C64 wiki on the CIA chip, I would say that your code probably isn't doing enough.

The port at DC02 controls the mode of the bits of port DC00. If a bit on DC02 is 1, the same bit of DC00 is in read/write mode and is used to control the keyboard and if it is 0, the bit on DC00 is in reads only mode and is used to read the joystick. DC03 provides the same function for DC01. I would expect your code. to:

  • save the state of DC02and DC03
  • set DC02 and DC03 to FF (read/write on all pins).
  • scan the keyboard
  • restore the states of DC02 and DC03
  • Sounds like you're on to something with the DDR registers! I notice that most keyboard routines set port A to all outputs (ones) and port B to all inputs (zeros). I'll try what happens if I just save/restore $dc02 and $dc03. – Nurpax May 8 '18 at 11:57
  • You're right about the DDR regs. I'd like to accept your answer although it's not quite what was needed for this particular keyboard case. – Nurpax May 8 '18 at 12:17
  • Added a snippet into my original question that fixes the problem. – Nurpax May 8 '18 at 12:27
  • @Nurpax Hi, your code snippet is a less general version of mine in which you assume you always want to monitor the joystick except when reading the keyboard. In your use-case, as long as it works, that's fine. The normal protocol on Stack Exchange sites if you come up with your own answer and no other answer is quite right is to write it up as an answer yourself and then accept it. Note, you may have to wait a bit (15 mins)? before accepting it. – JeremyP May 8 '18 at 13:45
  • Yep, I realize it is more general. I tried your save/restore version but it didn’t fix my joystick problem. I suppose I should’ve added DDR writes for my joystick code too and then use save/restore for keyboard. – Nurpax May 8 '18 at 13:58
1

An easy solution to my original problem of using this keyboard code while simultaneously reading joystick port 2 from $dc00 is as follows:

read_keyboard: {
    jsr Keyboard
    ldx #0
    stx $dc02 // put joystick port 2 register $dc00 to read mode
    rts 
}

The idea (as explained in JeremyP's answer) is to put the CIA#1 port A into read-only mode.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.