Episode 3-5: Taking Command of the Ship Controls
/actraiser
Synopsis: We implement some basic steering controls for our Space Ship using the C64 keyboard.
Download via dust: $ dust tutorials (select 'spritro')
Github Repository: Spritro Source Code on Github
- Episode 3-1: Spritro - An Intro with a Sprite
- Episode 3-2: Creating the Shapes - Hello SpritePad
- Episode 3-3: Loading Shapes and grasping Symbols
- Episode 3-4: Flying the Space Ship off Course
- Episode 3-5: Taking Command of the Ship Controls
- Episode 3-6: Custom Character Sets - Hello CharPad
- Episode 3-7: Creating Pseudo Timers for Color Cycle
- Episode 3-8: If the SID doesn't fit, use a bigger Hammer
- Episode 3-9: Greetings, Acknowledgments and Links
Steering the Ship
The Intro comes with some basic keyboard controls. You can hit the key D to steer the ship downwards to the bottom border area or U to fly upwards to the top. If we would use Kernel routines this would be a no-brainer as all you need to know about what key is currently pressed is stored by the Kernel in a specific memory locations. However, as usual, this is slow compared to accessing the hardware directly.
Once you understand how the different Keys are stored in memory and addressed using the CIAs the resulting routine is actually very small.
Querying the Keyboard Matrix
Your C64 has 66 Keys where SHIFT-LOCK and RESTORE being not part of the internal Keyboard Matrix. RESTORE is connected to the NMI, thus it triggers an non-maskable interrupt when pressed. SHIFT-LOCK is actually a switch and not a key per se so it is like-wise not detectable within the Keyboard Matrix.
All other 64 keys can be easily addressed and checked using the SCNKEY Kernal routine. However, as already pointed out we want to work without the Kernal, so we need to go one layer deeper into the hardware.
The Keyboard Matrix System can be imagined as 8x8 Grid where Rows and Columns can be individually accessed by the CIA-1 which is one of two Complex Interface Adapters (CIA 6526) in the Commodore C64.
The approach to check for an individual pressed key is actually not very hard. You first need to set up two so-called Data Direction Registers. Then you look at the Keyboard Matrix below and identify the row with your key in question. The value of that Row from that Table needs to be put into the Port Register A at $dc00. When this is done we just need to load the Port Register B $dc01 and test it against a Bit pattern to check if the appropriate key was pressed.
What needs to be considered is that the Bits in $dc00 and $dc01 are low-active, so if a key is pressed, a Bit is 0, if it is not pressed the Bit is set high.
Let's do an example!
Below is the matrix as organized between the two Port Register A and Port Register B. Next to each key the Screen Code is shown for the sake of completeness but it is not required for the following scan. The testing for keys without supplied Screen Code need some additional checking in other registers which we will not discuss at this point.
So let's check if the "x" key is currently pressed because we may want to exit a program in that case. If we follow the approach outlined above we need to set the Data Direction Registers first so reading is actually possible. Then we write the Bit Pattern of the third row into Port Register A and can afterwards query if Bit#7 of Port Register B to check if it is NOT set. When this is the case the 'x' key is pressed down.

Now for the code:
pra = $dc00 ; CIA#1 (Port Register A)
prb = $dc01 ; CIA#1 (Port Register B)
ddra = $dc02 ; CIA#1 (Data Direction Register A)
ddrb = $dc03 ; CIA#1 (Data Direction Register B)
lda #%11111111 ; CIA#1 Port A needs to be set to output
sta ddra
lda #%00000000 ; CIA#1 Port B needs to be set to input
sta ddrb
lda #%11111011 ; select third row
sta pra ; by storing $0b into pra
lda prb ; load current column information
and #%10000000 ; isolate 'x' key Bit which is Bit#7
beq exit ; if AND result is zero, then key was pressed
rts ; if not return
exit jmp $ea81 ; jmp to the general IRQ service routineWhen you have the table - it is really not hard to follow.
With this knowledge we can easily test for three different keys, the 'u'-key, the 'd'-key and the 'Space'-key and act accordingly, that is move the ship one Y-Coordinate up or down or exit the program. Additionally we want to add a boundary in the top and bottom of the screen so the ship can not vanish.
;=======================================
; check for a single key press
; symbols dor ddra, ddrb, pra,
; and prb were set in config_symbols.asm
;=======================================
check_keyboard
lda #%11111111 ; CIA#1 Port A set to output
sta ddra
lda #%00000000 ; CIA#1 Port B set to inputt
sta ddrb
check_space lda #%01111111 ; select row 8
sta pra
lda prb ; load column information
and #%00010000 ; test 'space' key to exit
beq exit_to_basic
check_d lda #%11111011 ; select row 3
sta pra
lda prb ; load column information
and #%00000100 ; test 'd' key
beq go_down
check_u lda #%11110111 ; select row 4
sta pra
lda prb ; load column information
and #%01000000 ; test 'u' key
beq go_up
rts ; return
go_up lda $d001
cmp #$1e ; check Y-coord whether we are too high
beq skip ; if top of screen reached, skip
dec $d001 ; decrease y-coord for sprite 1
rts
go_down lda $d001 ; increase y-coord for sprite 1
cmp #$ff ; check Y-coord whether whether we are too low
beq skip ; if bottom of border was reached, skip
inc $d001
rts
exit_to_basic lda #$00
sta $d015 ; turn off all sprites
jmp $ea81 ; jmp to regular interrupt routine
rts
skip rts ; don't change Y-CoordinateAs you can see, it is not very hard to check for a specific key press and take actions accordingly. We check for three keys consecutively before we either return with rts or if we identified one of the keys take some action before returning.
-act