Espamática
ZX SpectrumRetroZ80 Assembly

ZX Spectrum Assembly, Pong – 0x02 Controls keys

In this chapter of ZX Spectrum Assembly, we will learn how to read the keyboard without using the ROM routines. We will develop the routine that checks if the control keys of our game have been pressed, and returns which keys have been pressed.

Translation by Felipe Monge Corbalán

Table of contents

Control keys

The keyboard of the ZX Spectrum is divided into eight half rows, each containing five keys.

When evaluating whether a key in a half-row has been pressed, the values come in one byte, in bits 0 to 4, whose values are one if it has not been pressed and zero if it has been pressed. Bit 0 is the key furthest from the centre (Caps Shift, A, Q, 1, 0, P, Enter, Space) and 4 is the key closest to the centre (V, G, T, 5, 6, Y, H and B).

Each half row is identified by a number:

Half-rowHexadecimal valueBinary Value
Caps Shift-V$FE1111 1110
A-G$FD1111 1101
Q-T$FB1111 1011
1-5$F71111 0111
0-6$EF1110 1111
P-Y$DF1101 1111
Enter-H$BF1011 1111
Space-B$7F0111 1111
ZX Spectrum Assembly, Pong

Note that to calculate the value of a half row, the one before or the one after, only circular bit rotations (RLC, RRC) are needed.

Inside the Pong folder we create the Step02 folder, and inside it the main.asm and controls.asm files.

The routine we are going to use to check the controls can be found in Santiago Romero’s course: Compiler Software’s Z80 Assembly Course. You can find this course on the speccy.org wiki.

The controls we will use are A-Z for player one and 0-O for player two.

We will develop in controls.asm a routine to check if any of the control keys have been pressed, which will return the keys pressed in the D register, using bit 0 for the A key, bit 1 for the Z key, bit 2 for the 0 key and bit 3 for the O key. The value of these bits is one if the key was pressed and zero otherwise.

The routine first resets the D register to zero:

ASM
ScanKeys:
ld   d, $00

It then checks that the A button has been pressed:

ASM
scanKeys_A:
ld   a, $fd
in   a, ($fe)	
bit  $00, a
jr   nz, scanKeys_Z
set  $00, d

With LD A, $FD we load the identifier of the half row A-G ($FD = 11111101) into A.

Next, with IN A, ($FE) we read the input port $FE (254) and leave the value at A. The input port $FE is the port from which we read the keyboard status.

The next step is to check whether key A has been pressed; we use the instruction BIT $00, A, which evaluates the status of bit 0 of register A. If the bit is zero, the Z flag is set, otherwise it is disabled.

With the following instruction, JR NZ, scanKeys_Z, if the bit becomes one, it jumps to evaluate the Z key press.

If the bit is zero, we set bit 0 of register D, SET $00, D, to indicate that key A has been pressed.

The next step is to check that the Z key has been pressed:

ASM
scanKeys_Z:
ld   a, $fe
in   a, ($fe)
bit  $01, a
jr   nz, scanKeys_0
set  $01, d

The difference with the A key check is that we load the Caps Shift-V half stack, LD A, $FE, into A, check the status of bit 1 corresponding to the Z key, BIT $01, A, if the key was pressed we jump to checking the 0 key press, JR NZ, scanKeys_0, and finally, we set bit 1 of D, SET $01, D, if the Z key was pressed.

It is possible to press the A and Z buttons at the same time. In this case, we deactivate the indicators to indicate that neither key has been pressed. The other option would be to leave the indicators of both keys pressed and move the character up and then down, leaving it where it was.

Let’s check that the two keys have been pressed and, if so, deactivate the corresponding bits:

ASM
ld   a, d
sub  $03	
jr   nz, scanKeys_0
ld   d, a

First we load the value of D in A, LD A, D, and check if the value is three, SUB $03, in which case both keys would have been pressed. If the result is not zero, the two keys have not been pressed and we jump to check the 0 key press, JR NZ, scanKeys_0. If it is zero, we set D to zero, LD D, A; A is already zero.

We could have used the CP instruction to evaluate the value of A with another register, a number or a value in memory pointed to by (HL), (IX + N) or (IY + N). CP subtracts one of these values from the value of register A; although it does not modify A, it does modify the pointers (register F), as follows:

Flag valueMeaning
ZA = Value
NZA <> Value
CA < Value
NCA >= Value
ZX Spectrum Assembly, Pong

If we use CP, we must set A to zero before LD D, A, e.g. with XOR A.

The AND, OR and XOR instructions have A as their target and the result they give at bit level is as follows:

OperationBit 1Bit 2Result
AND111
100
010
000
OR111
101
011
000
XOR110
101
011
000
ZX Spectrum Assembly, Pong

The list would look like this:

ASM
ld   a, d
cp   $03
jr   nz, scanKeys_0
xor  a
ld   d, a

We would use one byte and seven clock cycles with XOR A.

Finally, we need to check that the 0 and O keys have been pressed, and that both keys have been pressed at the same time. The code is almost the same as we have seen so far, let’s see the complete code of the routine:

ASM
;------------------------------------------------------------------
; ScanKeys
; Scans the control keys and returns the pressed keys.
; Output: D -> Keys pressed.
;         Bit 0 -> A pressed 0/1.
;         Bit 1 -> Z pressed 0/1.
;         Bit 2 -> 0 pressed 0/1.
;         Bit 3 -> O pressed 0/1.
; Alters the value of the AF and D registers.
;------------------------------------------------------------------
ScanKeys:
ld   d, $00                ; Sets the D register to 0

scanKeys_A:
ld   a, $fd                ; Load in A the A-G half-stack
in   a, ($fe)              ; Read status of the semi-stack
bit  $00, a                ; Checks if the A has been pressed
jr   nz, scanKeys_Z        ; If not clicked, skips
set  $00, d                ; Set the bit corresponding to A to one

scanKeys_Z:
ld   a, $fe                ; Load in A the CS-V half-stack
in   a, ($fe)              ; Read status of the half-stack
bit  $01, a                ; Checks whether Z has been pressed
jr   nz, scanKeys_0        ; If not clicked, skips
set  $01, d                ; Sets the bit corresponding to Z to one

; Check that the two arrow keys have not been pressed
ld   a, d                  ; Load the value of D into A
sub  $03                   ; Checks whether A and Z have been pressed
                           ; at the same time
jr   nz, scanKeys_0        ; If not pressed, skips
ld   d, a                  ; Sets D to zero

scanKeys_0:
ld   a, $ef                ; Load the half-stack 0-6
in   a, ($fe)              ; Read status of the semi-stack
bit  $00, a                ; Checks if 0 has been pressed
jr   nz, scanKeys_O        ; If not pressed, skip
set  $02, d                ; Set the bit corresponding to 0 to a one

scanKeys_O:
ld   a, $cf                ; Load the P-Y half-stack
in   a, ($fe)              ; Read status of the semi-stack
bit  $01, a                ; Checks if the O has been pressed
ret  nz                    ; If not pressed, jumps to
set  $03, d                ; Sets the bit corresponding to O to one

; Check that the two arrow keys have not been pressed
ld   a, d                  ; Load the value of D into A
and  $0c                   ; Keeps the 0 and O bits
cp   $0c                   ; Check if the two keys have been pressed
ret  nz                    ; If they have not been pressed, it exits
ld   a, d                  ; Pressed, loads the value of D in A
and  $03                   ; Takes the bits of A and Z
ld   d, a                  ; Load the value in D

ret

The main difference from the A-Z keystroke check is that it checks whether the two keys were pressed at the same time.

Before checking that the bits of register D corresponding to 0 and O ($0C = 0000 1100) are active, it is necessary to maintain only these bits, otherwise, if A or Z were pressed, CP $0C would never return zero, so AND $0C was inserted before this instruction to maintain the value of bits 2 and 3.

The second difference is the way we reset bits 2 and 3 when 0 and O are pressed at the same time.

To check if A and Z were pressed at the same time, we put SUB $03 and LD D, A, because in A we only had these keystrokes, but now, in addition to whether 0 and O were pressed, we also have the keystrokes of A and Z, and if we were to LD D, A directly, we would destroy this information.

To avoid destroying this information, we load the value of register D into A, LD A, D, then we keep only the value of bits 0 and 1, AND $03, and we load the value back into D, LD D, A. In this way we have set the value of bits 2 and 3 to 0, but without destroying the value of bits 0 and 1.

We can optimise by replacing LD A, D and AND $03 with XOR D, which has the same effect as the other two lines, but uses four clock cycles and one byte.

If the value of A is 00001100 and the value of D is 00001101 after XOR D, the value of A is 00000001.

All that’s left now is to test the routine. We will paint the value of D in the upper left corner when it returns from the keystroke check routine. We will write the code in the main.asm file.

Specify the address where the program is loaded:

ASM
org  $8000

We point HL at the top left corner of the screen:

ASM
ld   hl, $4000

We make an infinite loop that calls ScanKeys and loads the value of the D register into the upper left corner of the window:

ASM
Loop:
call ScanKeys
ld   (hl), d
jr   Loop

Finally, we include the controls.asm file and tell PASMO the address to call when the programme is loaded.

ShellScript
pasmo --name ZX-Pong --tapbas main.asm pong.tap pong.log

The result of the programme will be something like this:

The final code of the main.asm file will look like this:

ASM
; Checks the operation of the A-Z and 0-O controls.
; Paints the representation of the keys pressed.
org  $8000
ld   hl, $4000	            ; HL = first screen position

Loop:
call ScanKeys 	            ; Scan for keystrokes
ld   (hl), d                ; Paints the keystroke display
jr   Loop                   ; Infinite loop

include "controls.asm"

end  $8000

We have left one optimisation, which we will see in the last chapter of ZX-Pong’s development, which will save one clock cycle in checking each keystroke, for a total of four clock cycles of savings in the ScanKeys routine.

ZX Spectrum Assembly, Pong

In the next ZX Spectrum Assembly chapter, we will paint the paddles and the centre line.

Download the source code from here.

ZX Spectrum Assembly, Pong by Juan Antonio Rubio García.
Translation by Felipe Monge Corbalán.
This work is licensed to Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0).
Any comments are always welcome.



Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies

ACEPTAR
Aviso de cookies

Descubre más desde Espamática

Suscríbete ahora para seguir leyendo y obtener acceso al archivo completo.

Seguir leyendo