Dustlayer

Retro means old but cool.

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


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.

​It's easy to check for a pressed key with the CIA-1
​It's easy to check for a pressed key with the CIA-1

Now for the code:​

check_x.asm
            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 routine

When 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.​

sub_check_key.asm
;=======================================
; 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-Coordinate

As 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