It is important to know how to program Joystick Input Handling on a C64. How else should we know what the player does? Read on to learn how this works.
The Joystick at the C64 can be read at the Memory Addresses $DC01 for Port 1 and $DC00 for Port 2 of the Complex Interface Adapter (CIA). If the examined Bit is 0, then the switch is closed and the corresponding action is active. Otherwise the Bit is 1.
There is a mapping from Bits to Direction/Fire Action:
Bit in Memory Address | Joystick Action |
---|---|
1 (%0000 0001) | up / forward |
2 (%0000 0010) | down / backward |
3 (%0000 0100) | left |
4 ( %0000 1000) | right |
5 (%0001 0000) | fire |
Let us look at an example where we put this knowledge into action.
C64 Program Example for Joystick Input Handling
The Complex Interface Adapter (CIA) handles the two 9-pin game ports in the C64. So you have a maximum of two Joysticks by default. A C64 Joystick is more or less digital. The switches inside open and close in alignment to axis movements and button presses. As already described above, the Bit in the memory address will be 0 if the switch is closed, or 1 otherwise.
This example will focus on the Joystick Input. Therefore we will not put fancy graphics or animations on the screen.
We will map the four directions of the joystick to the numbers 1 (left), 2 (right), 3 (up) and 4 (down). If you push the Joystick in any direction the corresponding number will appear on the screen. The fire button will print the character F.
Look at the Code and its explanation in order to understand how this works. We will discuss later where you can go from here.
Please note that our Example will only work with the Joystick in Port 1
The Assembly Code
This is a complete program for Joystick Input Handling on a C64 in Assembly Language.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
;**************************************** ;* Handling Joystick Input * ;* * ;* Default ACME Configuration Assembler * ;**************************************** *=$0801 ; Starting Address BASIC + 1 => SYS 2049 !byte $0C,$08,$40,$00,$9E,$20,$32,$30,$36,$32,$00,$00,$00 ; BASIC CODE: 1024 SYS 2062 jsr $E544 ; clear screen .input_left_check: lda #$04 ; mask left movement (4 == bit 3 == %0000 0100) bit $DC01 ; bitwise "and" with joystick port 1 bne .input_right_check ; if not active (==1), go to .input_right_check ; else print 1 lda #$31 ; load character '1' into register A jsr $FFD2 ; print character to screen .input_right_check: lda #$08 ; mask left movement (8 == bit 4 == %0000 1000) bit $DC01 ; bitwise "and" with joystick port 1 bne .input_up_check ; if not active (==1), go to .input_up_check ; else print 2 lda #$32 ; load character '2' into register A jsr $FFD2 ; print character to screen .input_up_check: lda #$01 ; mask left movement (1 == bit 1 == %0000 0001) bit $DC01 ; bitwise "and" with joystick port 1 bne .input_down_check ; if not active (==1), go to .input_down_check ; else print 3 lda #$33 ; load character '3' into register A jsr $FFD2 ; print character to screen .input_down_check: lda #$02 ; mask left movement (2 == bit 2 == %0000 0010) bit $DC01 ; bitwise "and" with joystick port 1 bne .input_fire_check ; if not active (==1), go to .input_fire_check ; else print 4 lda #$34 ; load character '4' into register A jsr $FFD2 ; print character to screen .input_fire_check: lda #$10 ; mask left movement (16 == bit 5 == %0001 0000) bit $DC01 ; bitwise "and" with joystick port 1 bne .input_left_check ; if not active (==1), go back to .input_left_check ; else print F (for FIRE) lda #$46 ; load character 'F' into register A jsr $FFD2 ; print character to screen jmp .input_left_check ; go back to .input_left_check to start over |
This example should run without modification if you use an ACME compiler like the on in the C64 studio.
If you assemble this program, start it and randomly press into the directions or the button on the joystick, you will get something like this:
Line by Line Explanation
The first lines are very similar to the ones I showed you in my C64 Machine Code Hello World Example. We set the start address of our program right after the beginning of the BASIC memory. Then we use SYS to call our Assembly code.
1 2 3 |
*=$0801 ; Starting Address BASIC + 1 => SYS 2049 !byte $0C,$08,$40,$00,$9E,$20,$32,$30,$36,$32,$00,$00,$00 ; BASIC CODE: 1024 SYS 2062 |
At the beginning we clear the screen. In order to do that we jump to the built-in subroutine that does what we want. It is stored at $E544.
1 |
jsr $E544 ; clear screen |
The label input_left_check marks the beginning of our loop. Now we check every bit for if there is something to do. Initially we load a bit mask into the Register A. We have to check if the third Bit is 1 or 0, therefore we mask the third Bit with 1. In the next line we do a bitwise and with the content in Register A.
If the result is 0, the switch is closed which means that the player moves the joystick to the left. In this case we load the character ‘1’ into the A register (represented by Hex Code 31) and print it to the screen. This is done by the built in routine CHROUT which is located at memory address $FFD2.
If the result is not equal to zero, we jump to the label input_right_check where we will proceed checking directions.
1 2 3 4 5 6 7 8 |
.input_left_check: lda #$04 ; mask left movement (4 == bit 3 == %0000 0100) bit $DC01 ; bitwise "and" with joystick port 1 bne .input_right_check ; if not active (==1), go to .input_right_check ; else print 1 lda #$31 ; load character '1' into register A jsr $FFD2 ; print character to screen |
The code for the checks of right, up and down are similar to the check left. The only things that change are the bitmask (according to the direction) and the character that will be printed to the screen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
.input_right_check: lda #$08 ; mask left movement (8 == bit 4 == %0000 1000) bit $DC01 ; bitwise "and" with joystick port 1 bne .input_up_check ; if not active (==1), go to .input_up_check ; else print 2 lda #$32 ; load character '2' into register A jsr $FFD2 ; print character to screen .input_up_check: lda #$01 ; mask left movement (1 == bit 1 == %0000 0001) bit $DC01 ; bitwise "and" with joystick port 1 bne .input_down_check ; if not active (==1), go to .input_down_check ; else print 3 lda #$33 ; load character '3' into register A jsr $FFD2 ; print character to screen .input_down_check: lda #$02 ; mask left movement (2 == bit 2 == %0000 0010) bit $DC01 ; bitwise "and" with joystick port 1 bne .input_fire_check ; if not active (==1), go to .input_fire_check ; else print 4 lda #$34 ; load character '4' into register A jsr $FFD2 ; print character to screen |
The last check adds one little extra. After the fire button there is nothing left to check. If we do nothing the program will end and return to BASIC. Therefore we jump back to the first check, our input_left_check and start over. This makes for an endless loop.
1 2 3 4 5 6 7 8 9 10 |
.input_fire_check: lda #$10 ; mask left movement (16 == bit 5 == %0001 0000) bit $DC01 ; bitwise "and" with joystick port 1 bne .input_left_check ; if not active (==1), go back to .input_left_check ; else print F (for FIRE) lda #$46 ; load character 'F' into register A jsr $FFD2 ; print character to screen jmp .input_left_check ; go back to .input_left_check to start over |
You should have noticed that this program only checks joystick input and does nothing else. In a real program you would not simply jump back to the first input check but rather do something else first.
The C64 Joystick has eight directions
The C64 Joystick can move up, down, left and right. But you can also move up and right at the same time. The same is true for up/left, down/right and down/left. This makes a total of eight directions that you can move to.
What happens if we press right/down at the same time in our example program? The number ’24’ will print, because we show 2 (right) and 4 (down) at the same time.
Complex Interface Adapter
The Complex Interface Adapter is an Interface Chip. It controls the I/O and the internal timer (clock) of the C64. Inside the C64 there are two identical CIAs. The different usage of the chips is specific to the C64.
CIA1 has the Memory Address Area $DC00-$DCFF and controls Keyboard, Joystick, Paddles, Datasette and IRQ Control.
CIA2 has the Memory Address Area $DD00-$DDFF and controls the Serial Bus, RS-232. VIC memory and NMI control.
You can see that we only need the CIA1 for our example.
Further Exploration
First of all you could make the program read inputs from a Joystick in Port 2. This should be easy enough and will help you to internalize the new knowledge. You could use the numbers 5,6,7 and 8 for the directions and trigger a space for the fire button of the Joystick #2.
The next step would be moving sprites instead of printing numbers. You would have to create a Sprite, put it in the Sprite Register and show it on the screen. Then you would have to manipulate the Sprite Position as reaction to the user input. After that you could trigger sprite animations.
Conclusion
Now you know how to program joystick input handling on the C64. As you can see it is not that hard. You need to know which Bits to check in the correct CIA1 Memory Address. The you simply map the Inputs to Actions like showing numbers on the screen or moving a sprite. Just keep in mind that the two Joystick Ports have different Addresses in the CIA1 Memory.