Mastering Machine Code on Your ZX81
By Toni Baker

Sinclair ZX Spectrum
SCANNING THE KEYBOARD
Now it's time to explore how we can make use of some of the other subroutines that are remarkably well-hidden within the ROM. Specifically we'll cover two of these subroutines, which between them will enable us to scan the keyboard and locate which, if any, of the keys on the keyboard are being depressed. On the NEW ROM we can make use of these subroutines just by calling them, but we can't on the OLD ROM because they're simply not there. For the benefit of the people with OLD ROMs I shall include a section at the end of this chapter explaining how these programs may be made to work by actually inputting these subroutines yourselves. This section will also be of interest to those of you with NEW ROMs, since it will give you an insight into how the subroutines actually work.

The first such subroutine is an amazing little keyboard scan, which begins at address 02BB. It may be accessed simply by calling that address, i.e. CALL KSCAN, or CDBB02 in hex. It doesn't actually produce a very usable answer though. Let's see exactly what it does do.

It returns a value to the HL register pair. Actually it returns seperate and independent values - one to H and one to L. Here's how the value of L is interpreted:

Imagine the keyboard (excluding SHIFT) divided up into eight horizontal sections, each containing five keys (except for section zero which only contains four, because SHIFT is ommitted). Notice how each section has a corresponding number between zero and seven. Now, if there is no key depressed then L will return a value FF. However, if one or more keys are depressed, then the appropriate BIT (of L) will be reset to zero. In other words, if you are pressing Q, W, E, R, or T then bit 2 will be reset - if you are pressing B, N, M, full-stop, or space, then bit 7 will be reset. This means that L can return the following:

                                   BINARY    HEX
If no key is depressed             11111111  FF
If a section 0 key is depressed    11111110  FE
If a section 1 key is depressed    11111101  FD
If a section 2 key is depressed    11111011  FB
If a section 3 key is depressed    11110111  F7
If a section 4 key is depressed    11101111  EF
If a section 5 key is depressed    11011111  DF
If a section 6 key is depressed    10111111  BF
If a section 7 key is depressed    01111111  7F

+------------------------------+------------------------------+
| SECTION 3                    |                    SECTION 4 |
| ( 1 ) ( 2 ) ( 3 ) ( 4 ) ( 5 )|( 6 ) ( 7 ) ( 8 ) ( 9 ) ( 0 ) |
+-+----------------------------+-+----------------------------+-+
  | SECTION 2                    |                    SECTION 5 |
  | ( Q ) ( W ) ( E ) ( R ) ( T )|( Y ) ( U ) ( I ) ( O ) ( P ) |
  +-+----------------------------+-+----------------------------+-+
    | SECTION 1                    |                    SECTION 6 |
    | ( A ) ( S ) ( D ) ( F ) ( G )|( H ) ( J ) ( K ) ( L ) (ENT) |
    +--+-----------------------+---+--------------------------+---+
       | SECTION 0             |                    SECTION 7 |
  (SHF)|( Z ) ( X ) ( C ) ( V )|( B ) ( N ) ( M ) ( . ) (   ) |
       +-----------------------+------------------------------+

As an exercise see if you could work out what L would be if both S and P were depressed at the same time.

The value returned by H is determined by a similar principle, but notice how the keyboard is divided up here - not horizontally but vertically. Notice also that the SHIFT key has a section all to itself - section 0. Now if you press key S for instance then H will return FB (in binary 11111011). We have already seen that L would give FB as well, so that HL returns FBFB. Can you see why it is impossible for this value to be obtained from any other key?

   1     2     3     4     5     5     4     3     2     1
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
 \     \     \     \     \     \     \     \     \     \     \
  \( 1 )\( 2 )\( 3 )\( 4 )\( 5 )\( 6 )\( 7 )\( 8 )\( 9 )\( 0 )\
   \     \     \     \     \     \     \     \     \     \     \
    \     \     \     \     \     \     \     \     \     \     \
     \( Q )\( W )\( E )\( R )\( T )\( Y )\( U )\( I )\( O )\( P )\
      \     \     \     \     \     \     \     \     \     \     \
       \     \     \     \     \     \     \     \     \     \     \
        \( A )\( S )\( D )\( F )\( G )\( H )\( J )\( K )\( L )\(ENT)\
         +-----+     +     +     +     +     +     +     +     +     +
   +-----+    /     /     /     /     /     /     /     /     /     /
   |(SHF)|   /( Z )/( X )/( C )/( V )/( B )/( N )/( M )/( . )/(   )/
   +-----+  +-----+-----+-----+-----+-----+-----+-----+-----+-----+
      0        2     3     4     5     5     4     3     2     1

      N        N     N     N     N     N     N     N     N     N
      O        O     O     O     O     O     O     O     O     O
      I        I     I     I     I     I     I     I     I     I
      T        T     T     T     T     T     T     T     T     T
      C        C     C     C     C     C     C     C     C     C
      E        E     E     E     E     E     E     E     E     E
      S        S     S     S     S     S     S     S     S     S

Now let's see what would happen if you pressed SHIFT S. Both bits zero and two would be reset giving, in binary, 11111010. In hex this is FA, so HL would return as FAFB - which is different to the value produced without shift. We can see the precise effect of SHIFT from the table:

                                   WITHOUT SHIFT   WITH SHIFT
                                   BINARY    HEX   BINARY    HEX
If no key is depressed             11111111  FF    11111110  FE
If a section 1 key is depressed    11111101  FD    11111100  FC
If a section 2 key is depressed    11111011  FB    11111010  FA
If a section 3 key is depressed    11110111  F7    11110110  F6
If a section 4 key is depressed    11101111  EF    11101110  EE
If a section 5 key is depressed    11011111  DF    11011110  DE

It should by now be reasonably clear how each individual key, with or without shift, produces its own unique code in the HL register pair. If two keys are both in the same horizontal section they cannot possibly both be in the same vertical section. Note that SHIFT on its own returns a value of FEFF which is not the same as no key depression at all.

The subroutine which I've called KSCAN does have one big disadvantage though - it will completely wipe out the previous values of all the registers! If you want to preserve them you'll have to make use of the stack as follows:

F5                  PUSH AF
C5                  PUSH BC
D5                  PUSH DE
CDBB02              CALL KSCAN
D1                  POP DE
C1                  POP BC
F1                  POP AF

Now we want to turn these rather obscure numbers into real character codes. It just so happens that all of these codes are rather cleverly stored in the ROM beginning at address 007E. By "rather cleverly" I mean in a most convenient order, as follows: First the straightforward characters:

007E    ZXCV ASDFG QWERT 12345 09876 POIUY newline LKJH space .MNB

(There are no spaces between the characters - they are printed here to make the ordering more obvious.) Then the shift characters:

00A5    :;?/ STOP LPRINT SLOW FAST LLIST "" OR STEP <= <> EDIT AND THEN
        TO cursor-left RUBOUT GRAPHICS cursor-right cursor-up cursor-down
        ")($ >= FUNCTION =+- ** £,><*

Can you see how the ordering relates to which sections the key lies in? We could quite easily write a subroutine now to convert from the strange number we already have in HL to an address between 007E and 00CB (the last item in the table - the *) but it turns out that we don't need to because that nice man Uncle Clive has already done it for us with a subroutine which I shall call FINDCHR beginning at 07BD. The ROM people probably have their own name for it but they keep it shrouded in mystery. The subroutine performs the following task - given a value as defined above, in the BC register, it will work out the address at which the appropriate character is stored - the final result ending up in HL.

It does have a problem though. If you're not pressing a key then surely you shouldn't end up with a character to print! You'll have to prevent this yourself. One way would be as follows. Notice though how we move the result from the first subroutine into the BC register before calling the second.

CDBB02    START     CALL KSCAN
44                  LD B,H
4D                  LD C,L
51                  LD D,C
14                  INC D
3E00                LD A,00
2804                JR Z,NOCHR
CDBD07              CALL FINDCHR
7E                  LD A,(HL)
...       NOCHR     ...
                    rest of program

There are several things to note about this example. Firstly that two seperate instructions, LD B,H and LD C,L are needed to transfer HL to BC since there is no single instruction LD BC,HL. Secondly that the condition JR Z means if D is zero, not A - LD does not alter any of the flags. If D is zero after being incremented then it must have been FF beforehand, which means that L must have been FF after it came out of the first subroutine. This is the check that a key is being pressed. A is loaded with zero and if no key is pressed it remains zero, otherwise it takes on the code of whichever character you're touching on the keyboard.

There is here a slight ambiguity in that the zero is also produced if you press space. You could use LD A,01 instead of LD A,00 since the character whose code is one (character-01) is not available from the keyboard. Now there is no ambiguity since zero means space and one means no character is being pressed. If you have SLOW at your disposal you could omit LD A,00 altogether and use JR Z,START instead of JR Z,NOCHR. Now the program will WAIT until a key is pressed before continuing. Without SLOW it will still wait but you'll have to suffer a blank screen in the meantime.

The A register now contains a value corresponding precisely to the function INKEY$. In this way real time games are just as feasible in machine code as they are in BASIC.

Another interesting part of the ROM is the very last bit - the half of a K that runs from 1E00 to 1FFF. It's not a subroutine, it's a table - a very long table - actually the longest table in ROM. It stores the dot pattern of every symbol used by the computer - that is all of the printable characters. It takes eight bytes to store a single character symbol, so for example, the characters A, B and C are represented, in binary, by:

0 0 0 0 0 0 0 0    0 0 0 0 0 0 0 0    0 0 0 0 0 0 0 0
0 0 1 1 1 1 0 0    0 1 1 1 1 1 0 0    0 0 1 1 1 1 0 0
0 1 0 0 0 0 1 0    0 1 0 0 0 0 1 0    0 1 0 0 0 0 1 0
0 1 0 0 0 0 1 0    0 1 1 1 1 1 0 0    0 1 0 0 0 0 0 0
0 1 1 1 1 1 1 0    0 1 0 0 0 0 1 0    0 1 0 0 0 0 0 0
0 1 0 0 0 0 1 0    0 1 0 0 0 0 1 0    0 1 0 0 0 0 1 0
0 1 0 0 0 0 1 0    0 1 1 1 1 1 0 0    0 0 1 1 1 1 0 0
0 0 0 0 0 0 0 0    0 0 0 0 0 0 0 0    0 0 0 0 0 0 0 0

Can you pick the letters A, B and C from the digits above? The pattern is held by the positions of the "ones" amongst the "zeroes". When they finally appear on your TV screen they look like this:

+-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+
| | | | | | | | |  | | | | | | | | |  | | | | | | | | |
+-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+
| | |@|@|@|@| | |  | |@|@|@|@|@| | |  | | |@|@|@|@| | |
+-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+
| |@| | | | |@| |  | |@| | | | |@| |  | |@| | | | |@| |
+-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+
| |@| | | | |@| |  | |@|@|@|@|@| | |  | |@| | | | | | |
+-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+
| |@|@|@|@|@|@| |  | |@| | | | |@| |  | |@| | | | | | |
+-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+
| |@| | | | |@| |  | |@| | | | |@| |  | |@| | | | |@| |
+-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+
| |@| | | | |@| |  | |@|@|@|@|@| | |  | | |@|@|@|@| | |
+-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+
| | | | | | | | |  | | | | | | | | |  | | | | | | | | |
+-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+  +-+-+-+-+-+-+-+-+

Suppose we now wished to reconstruct these letters in an enlarged form - using a pixel (quarter-square) for each dot. This means that each character we print should be a graphics character (space and inverse-space both count as graphics characters) chosen so that the correct quarters are black.

There are two ways of doing this. One is to make use of the NEW ROM character codes, in which the graphics are arranged in a very clever order - unfortunately we would not be able to adapt this system to the OLD ROM. The second is to include sixteen bytes of data within our program representing the graphics symbols in any order we care to choose. Let's take a look at the first method first.

Suppose the bottom right corner is WHITE. If we give the other pixels numbers 1, 2 and 4 then simply adding them up gives the required character code. You can check this by comparing the diagram below with the character set in the Sinclair manual.

If the bottom right hand corner is BLACK then we need to give the other pixels the numbers -1, -2, and -4. To work out the code of any graphics symbol here we start off with the number 135 (decimal) and subtract appropriately the required number for each black pixel. Again you can check this by comparing the diagram below with the Sinclair manual.

Incidently it is worth pointing out here that many copies of the Sinclair manual incorrectly give the character of 135 as character-07. This is a misprint - it should of course be character-87. Try typing PRINT CHR$ 135 to check. Character seven is character-07 - the manual gives this correctly.

+---------+---------+    +---------+---------+
|         |         |    |         |         |
|    1    |    2    |    |   -1    |   -2    |
|         |         |    |         |         |
|         |         |    |         |         |
+---------+---------+    +---------+---------+
|         |         |    |         |/////////|
|    4    |  white  |    |   -4    |//black//|
|         |         |    |         |/////////|
|         |         |    |         |/////////|
+---------+---------+    +---------+---------+

[Thunor: The author is saying that if the bottom-right is white then you add-up the values in the other squares if they are black, and if the bottom-right is black then you subtract the values in the other squares from 135 if they are black. A quick peek at the NEW ROM character set in appendix five illustrates this.]

The character codes of the OLD ROM graphics symbols are unfortunately rather random, so there is no single system for working out the code, given which pixels should be black and which should be white. In order that the program to follow should work on both ROMs we shall adopt a slightly different method. Instead of distinguishing two different cases (that is the colour of the bottom right-hand pixel) we shall treat every quarter-square the same, and code them as follows:

+---------+---------+
|         |         |
|    8    |    4    |
|         |         |
|         |         |
+---------+---------+
|         |         |
|    2    |    1    |
|         |         |
|         |         |
+---------+---------+

We should then have to include in our program a DATA section which lists the graphics symbols in the order space graphic-34628TQ1Y5W7RE inverse-space

Move RAMTOP to address 4380 (this is a hex address) by typing POKE 16388,128 POKE 16389,67 then NEW. Now load the following program to address 4380 (in decimal this is 17280, meaning 1K users will still be able to run it). As it stands this program is best run in SLOW. We shall see how to alter it so that it will run in FAST later.

[Thunor: Unless you really want this to run in 1K I suggest you don't set RAMTOP this low because you're going to want to use HEXLD2 or HEXLD3 to type it in and SAVE it. I recommend HEXLD3 -- seeing as you've just written it or downloaded mine -- and writing to 4D00h (19712d), therefore the instruction 218043 LD HL,DATA below should read 21004D LD HL,DATA, and to start it type RAND USR 19728. NEW ROM users don't forget that HEXLD3 requires initialising for a new program.]

[NEW ROM ONLY]
00870483  DATA      DEFM "space graphic-346........"    This is the table of
02850681            DEFM "graphic-28TQ............."    graphics symbols in
01860582            DEFM "graphic-1Y5W............."    the required order.
03840780            DEFM "graphic-7RE inverse-space"  

[OLD ROM ONLY]
00070603  DATA      DEFM "space graphic-346........"    This is the table of
05820884            DEFM "graphic-28TQ............."    graphics symbols in
04880285            DEFM "graphic-1Y5W............."    the required order.
83868780            DEFM "graphic-7RE inverse-space"

[Both ROMs Continued]
CDBB02    START     CALL KSCAN         Wait for human to take
2C                  INC L              finger off of key.
20FA                JR NZ,START
CDBB02    WAIT      CALL KSCAN         Wait for new key to be
44                  LD B,H             pressed.
4D                  LD C,L
51                  LD D,C
14                  INC D
28F7                JR Z,WAIT
CDBD07              CALL FINDCHR       Locate appropriate
7E                  LD A,(HL)          character code.
A7                  AND A
17                  RLA                Multiply by eight, but
17                  RLA                return to BASIC if a non-
D8                  RET C              printable character is
17                  RLA                pressed.
1600                LD D,00
CB12                RL D
5F                  LD E,A
21001E              LD HL,CTABLE       Find start of dot  <- NEW ROM ONLY
21000E              LD HL,CTABLE       Find start of dot  <- OLD ROM ONLY
19                  ADD HL,DE          pattern.
0E04                LD C,04
0604      OUTERLOOP LD B,04
56                  LD D,(HL)          Transfer two lines of dots
23                  INC HL             into D and E
5E                  LD E,(HL)
23                  INC HL
E5                  PUSH HL
AF        INNERLOOP XOR A              Compute which graphics
CB12                RL D               character is to be
17                  RLA                printed.
CB12                RL D
17                  RLA
CB13                RL E
17                  RLA
CB13                RL E
17                  RLA
218043              LD HL,DATA         Get this character from
85                  ADD A,L            the table of graphics
6F                  LD L,A             symbols.
7E                  LD A,(HL)
D9                  EXX                Print this symbol.
F5                  PUSH AF            <- OLD ROM ONLY
CDE006              CALL PRPOS         <- OLD ROM ONLY
F1                  POP AF             <- OLD ROM ONLY
CD2007              CALL PRINT         <- OLD ROM ONLY
CD0808              CALL PRINT         <- NEW ROM ONLY
D9                  EXX
10E6                DJNZ INNERLOOP     Next print    <- NEW ROM ONLY
10E1                DJNZ INNERLOOP     Next print    <- OLD ROM ONLY
E1                  POP HL             position.
3E76                LD A,76            End of current line.
D9                  EXX
F5                  PUSH AF            <- OLD ROM ONLY
CDE006              CALL PRPOS         <- OLD ROM ONLY
F1                  POP AF             <- OLD ROM ONLY
CD2007              CALL PRINT         <- OLD ROM ONLY
CD0808              CALL PRINT         <- NEW ROM ONLY
D9                  EXX
0D                  DEC C
20D4                JR NZ OUTERLOOP    Next line begins.  <- NEW ROM ONLY
20CA                JR NZ OUTERLOOP    Next line begins.  <- OLD ROM ONLY
18AF                JR START           Start again.       <- NEW ROM ONLY
C9                  RET                                   <- OLD ROM ONLY

The program is now complete. Make sure you are in SLOW mode and start the program off by typing RAND USR 17296. DO NOT type RAND USR 17280 since this is purely data and will not run. You should see a completely blank screen. Press "C", and watch what happens. Now press "A". Interesting isn't it? Try experimenting with different keys to see what happens - and what happens when you run out of screen?

Download available for 16K ZX81 -> chapter10-enlarge.p]
You may have been confused by the use of the instruction EXX which was used four times in the above program. Its function is very easy to explain. As you know, the registers B, C, D, E, H, L, and A can [be] very easily manipulated, but there are also seven other registers, called B', C', D', E', H', L', and A' (pronounced A-dash or A-prime). These are not so easy to manipulate and can in practice only be used for storage purposes. The instruction EXX means exchange B and B', C and C', D and D', E and E', H and H', L and L'. Thus all the registers except A lose their previous values but take on the values of their alternative registers. Likewise the alternative registers take on the original values of the usual registers.

The reason we need to do this is because the ROM subroutine PRINT destroys the previous values of BC, DE, and HL. We could have preserved them by pushing them onto the stack, but EXX works just as well here and is only one instruction.

Lets take a closer look at the above program and sort out exactly what each bit does. First of all we find the right character code, which gets stored in the A register. The instruction AND A resets the carry flag to zero. RLA will then multiply A by two. Now we know that this character is on the keyboard and can be obtained in one touch, so it is not an inverse character. Rotating left then will move the leftmost bit, which must be a zero, into the carry flag. RLA a second time will again multiply by two (since we know the carry is zero), however, if the character is NOT PRINTABLE (such as newline or STOP) then bit 6 of the original value will be a one. This will now be moved into carry. The instruction RET C ensures that if this circumstance ever occurs the program will terminate.

Knowing then that the carry is still zero we can use RLA once more to multiply by two. Here however, bit 5, which [is] a necessary part of the character code, will be moved into the carry flag. To move the carry digit into D we use two instructions LD D,00 and RL D. D will then contain either zero or one. LD E,A ensures that register-pair DE now contains eight times the original value of A.

The other interesting part is the first nine lines of the INNER-LOOP. A is loaded with the first two bits of D and the first two bits of E. This gives a number between zero and fifteen which corresponds to the required graphics symbol. It is NOT the character code, it is the specially designed code we worked out earlier on. Notice how the NEXT bits of D and E are now automatically in place at the extreme left.

For those of you who do not have SLOW I suggest replacing the last instruction, JR START by RET. You could then have a surrounding BASIC program as follows:

10 RAND USR 17296
20 PAUSE 40000       <- NEW ROM ONLY
20 INPUT A$          <- OLD ROM ONLY
30 RUN

THE SUBROUTINES

OLD ROM users will by now be feeling quite envious at NEW ROM people for having these subroutines at their disposal. Of course there is a keyboard scan in the OLD ROM, but it isn't a subroutine - i.e. it doesn't end in RET. One call to it and you're stuck there forever! What we'll have to do is rewrite them ourselves. We can do this by taking all of the important bits from the subroutines in the NEW ROM.

First of all KSCAN. This is the required subroutine. Don't worry if there are parts of it you don't understand - all will become clear in due course.

21FFFF    KSCAN     LD HL,FFFF
01FEFE              LD BC,FEFE
ED78                IN A,(C)
F601                OR 01
F6E0      LOOP      OR E0
57                  LD D,A
2F                  CPL
FE01                CP 01
9F                  SBC A,A
B0                  OR B
A5                  AND L
6F                  LD L,A
7C                  LD A,H
A2                  AND D
67                  LD H,A
CB00                RLC B
ED78                IN A,(C)
38ED                JR O,LOOP
1F                  RRA
CB14                RL H
C9                  RET

Now - if you enter this subroutine into RAM you can then replace every CDBB02 in the chapter by a call to the appropriate address in RAM. The other subroutine you'll need to be able to emulate is FINDCHR. This may be done as follows.

1600      FINDCHR   LD D,00
CB28                SRA B
9F                  SBC A,A
F626                OR 26
2E05                LD L,05
95                  SUB L
85        LOOP      ADD A,L
37                  SCF
CB19                RR C
38FA                JR C,LOOP
0C                  INC C
C0                  RET NZ
48                  LD C,B
2D                  DEC L
2E01                LD L,01
20F2                JR NZ,LOOP
217D00              LD HL,KTABLE-1    <- NEW ROM ONLY
216B00              LD HL,KTABLE-1    <- OLD ROM ONLY
5F                  LD E,A
19                  ADD HL,DE
C9                  RET

The address 007D, referred to in my listing as KTABLE-1, is for the NEW ROM only. THE ADDRESS OF KTABLE IN THE OLD ROM IS 006C, and so this line should be changed to LD HL,006B. This is far easier to understand than the first subroutine. The second and third lines are rather interesting.

If you remember BC should contain a code corresponding to one of the keys at the start of the subroutine. Now bit zero of B is a one if SHIFT is not pressed, and zero if shift is pressed. SRA B will shift B to the right, will set bit 7 to one (do you remember the difference between SRA and SRL?), and will set the carry flag equal to the previous value of bit zero.

SBC A,A will first of all subtract A from A - effectively resetting it to zero - and will then subtract the carry flag. In other words, if SHIFT is pressed A will end up as 00, if SHIFT is not pressed A will end up as FF.

The fourth line, OR 26, will ensure that A is 26 for a shifted character, FF for a non-shifted character.

You should recall here that B contains information about which VERTICAL section the key is in, and C about which HORIZONTAL section. If you take a closer look at the order the characters are stored [in] in the keyboard table (KTABLE) which was shown a few pages back you'll see that the horizontal-section-number needs to be multiplied by five, and the vertical-section-number added to it, in order to find a specific key in the table. This is what the next part does:

L is loaded with 5 - the multiplying factor. Notice how the next two lines cancel each other out the first time round the loop. This is one way of adding L nought times should we need to. The next two lines are SCF and RR C. This is not the same thing as SRA C, since bit 7 could be zero (i.e. if a horizontal-section-7 key is pressed). Apart from shifting C to the right it also moves one bit into the carry. If this bit is a one we haven't found the right section yet and the loop is re-executed. Note that five is added each time round the loop. Note also that if A starts off as FF it is just as easy, if not easier, to think of it as minus-one.

Now that we're out of the loop, C should be all ones, that is, it should be FF, so that INC C should ensure that it becomes zero, so what's this RET NZ instruction for? Of course this condition is simply to check that you're not holding down two keys at once. What would C contain if you were?

LD C,B moves the vertical-section-data into the B register, so that the same loop may be used over again.

DEC L followed by LD L,1 looks confusing. Actually it's not. At the moment L is five, and so DEC L makes it four, which is NOT ZERO. LD L,1 doesn't alter the zero flag, so JR NZ,LOOP sends it back through the same loop, but this time checking the vertical sections, and only incrementing by one instead of five.

When it comes out of the loop DEC L will reduce to zero, so after reloading L with one JR NZ will not be satisfied and the program will continue.

LD HL,KTABLE-1. Why minus one? Well if there was a "real key" in the position where SHIFT is and you were pressing it then A would end up as zero. Since there isn't the smallest value A can end up as is one, which happens if you hold down "Z", hence LD HL,KTABLE-1 takes this into account.

LD E,A is effectively loading A into DE. This works because D is already zero - see the first line of the program. Then ADD HL,DE will find the correct address. Notice that we could have replaced these two instructions by ADD A,L followed by LD L,A. This has the advantage that the first instruction (LD D,00) becomes unnecessary, and that DE is not at all altered by the subroutine. The ROM however uses the version as listed.

KTABLE in the OLD ROM looks like this:

006C    ZXCV ASDFG QWERT 12345 09876 POIUY newline LKJH space .MNB
0093    :;?/ graphic-ASDFGQWERT NOT AND THEN TO cursor-left RUBOUT HOME
        cursor-right cursor-up cursor-down *)($" edit =+- ** £,>< OR

For the actual printing process itself, the instruction CALL PRINT for the NEW ROM should be replaced by PUSH AF/CALL PRPOS/POP AF/CALL PRINT. In HEX this is F5/CDE006/F1/CD2007.

The Character Table (CTABLE) which gives the dot patterns for the characters is located in the OLD ROM at address 0E00, rather than 1E00. Again it is stored at the very end of the ROM. All of the characters are slightly different.

The data for the table of graphics symbols in the character printing program should run 00 07 06 03 05 82 08 84 04 88 02 85 83 86 87 80 if the program is to be used with an OLD ROM. Replace the PAUSE 40000 BASIC statement given in the following text [previous listing] by INPUT A$.

Download available for 16K ZX81 -> chapter10-enlarge2.p.
[Addresses used: DATA is 4D00, START is 4D10, KSCAN is 4D61 and FINDCHR is 4D82.]

Download available for 16K ZX80 -> chapter10-enlarge.o.
[Addresses used: 4A1A to 4B3A is occupied by HEXLD3, DATA is 4B3B, START is 4B4B, KSCAN is 4BA5 and FINDCHR is 4BC6. Unfortunately I can't get this version to work. There's a possibility I may return to this to see if I can fix it, but I've already spent a lot of time checking it over. Feel free to send me your working version and I'll credit you in the page :)]

GRAFFITTI

It only requires a slight modification to the original version in order to make a really excellent program, demonstrating the immense speed which machine code offers over BASIC. In this program, GRAFFITTI, you touch a key and an enlarged version of the required symbol appears on the screen. In this program the whole screen is used (even the bottom two lines) thus allowing a total of forty-eight symbols on the screen. To load it move RAMTOP to any address not less than 4D00 and NEW (i.e. this can't be done in 1K - at least not in this version). The program is as follows:

2A0C40    GRAFFITTI LD HL,(D_FILE)     Set the print position
23                  INC HL             to the start of the screen.
220E40              LD (DF_CC),HL
36B0      START     LD (HL),B0         Print a cursor.
CDBB02    PAUSE     CALL KSCAN         Wait for human to take
2C                  INC L              finger off of key.
20FA                JR NZ,PAUSE
CDBB02    WAIT      CALL KSCAN         Wait for new key to
44                  LD B,H             be pressed.
4D                  LD C,L
51                  LD D,C
14                  INC D
28F7                JR Z,WAIT
CDBD07              CALL FINDCHR       Locate the correct
7E                  LD A,(HL)          character code.
A7                  AND A
17                  RLA                Multiply by eight, but
17                  RLA                return to BASIC if a
D8                  RET C              non-printable character
17                  RLA                is pressed.
1600                LD D,00
CB12                RL D
5F                  LD E,A
21001E              LD HL,CTABLE       Find start of dot  <- NEW ROM ONLY
21000E              LD HL,CTABLE       Find start of dot  <- OLD ROM ONLY
19                  ADD HL,DE          pattern.
0E04                LD C,04
0604      OUTERLOOP LD B,04
56                  LD D,(HL)          Transfer two lines of
23                  INC HL             dots into D and E.
5E                  LD E,(HL)
23                  INC HL
E5                  PUSH HL
AF        INNERLOOP XOR A              Computer which graphics
CB12                RL D               character is to be
17                  RLA                printed.
CB12                RL D
17                  RLA
CB13                RL E
17                  RLA
CB13                RL E
17                  RLA
21-DATA-address     LD HL,DATA         Get this character from
85                  ADD A,L            the table of graphics
6F                  LD L,A             symbols.
7E                  LD A,(HL)
2A0E40              LD HL,(DF_CC)      Print this character
77                  LD (HL),A          in required position.
23                  INC HL             Store new print
220E40              LD (DF_CC),HL      position.
10E3                DJNZ INNERLOOP
D5                  PUSH DE            Move print position
111D00              LD DE,001D         ready for next row
19                  ADD HL,DE          of symbols.
220E40              LD (DF_CC),HL
D1                  POP DE
E1                  POP HL
0D                  DEC C
20CF                JR NZ,OUTERLOOP
1180FF              LD DE,FF80         Move print position
2A0E40              LD HL,(DF_CC)      ready for next enlarged
19                  ADD HL,DE          character.
220E40              LD (DF_CC),HL
7E                  LD A,(HL)          Check for end of line.
FE76                CP 76
209B                JR NZ,START
116400              LD DE,0064         Move print position to
19                  ADD HL,DE          left of screen ready for
220E40              LD (DF_CC),HL      next enlarged character.
23                  INC HL
ED5B1040            LD DE,(VARS)       Check for end of screen.
ED52                SBC HL,DE
19                  ADD HL,DE
383A                JR C,START
C9                  RET

Download available for 16K ZX81 -> chapter10-graffitti.p.
[Addresses used: DATA is 4D00, GRAFFITTI is 4D10, KSCAN is 4D8E and FINDCHR is 4DAF. It's supposed to fill the entire screen but it only wraps one line. When I have some spare time I'll return to this and attempt to fix it.]

This program is intended to be run on a ZX81 in the SLOW mode. See if you can work out how to adapt it so that it will print inverse characters instead of ordinary ones. It may even be possible to offer a choice!

Sinclair ZX Spectrum

  Previous Page Back Next Page