Penguin Books

Creative Assembler How To Write Arcade Games








As one of Acornsoft's games programmers, Jonathan Griffiths was responsible for such widely popular programs as Snapper and JCB Digger for the BBC Microcomputer Model B and Acorn Electron He has prepared a cassette program, which can be used in conjunction with this book, available from Acornsoft Limited, c/o Vector Marketing Ltd, Denington Industrial Estate, Wellingborough, Northants
NN8 2RL.























Acknowledgements
Thanks are due to David Johnson-Davies for providing an earlier version of the first few chapters, Jim Dobson and Jeremy Bennett for helping in the writing of this book, and to Philippa Bush and Sharron Fellows for editing it Also, thanks are due to Orlando
M. Pilchard (Q.C.), Chris Jordan, Paul Hudson,Peter Cockerell, Dominic Verity, Jeremy San, Robert Macmillan, Paul Fellows, John Collins, Simon Hughes and Mark Holmes for proof reading


This book was written and prepared on a BBC Microcomputer Model B using the VIEW word processor



This book is dedicated to all the staff at Acornsoft























CREATIVE ASSEMBLER How To Write Arcade Games

Jonathan Griffiths






























The Penguin Acorn Computer Library is a joint venture, produced by Acomsoft Limited (in association with Pilot Productions Limited), and published by Penguin Books Limited

Penguin Books Ltd, Harmondsworth, Middlesex, England
Penguin Books, 40 West 23rd Street, New York, New York, 10010, U.S A.
Penguin Books Australia Ltd, Ringwood, Victoria, Australia
Penguin Books Canada Ltd, 2801 John Street, Markham, Ontario, Canada L3R 1~
Penguin Books (N.Z.) Ltd, 182-190 Wairau Road, Auckland 10, New Zealand

First published 1984

Copyright © Acornsoft Limited, 1984

All rights reserved
























Set in Palatino by Repro Graphics, 61 Cromwell Road, Southampton

Colour origination by RCS Graphics Ltd, 39-40 Springfield Mills, Farsley, Pudsey, Leeds, and MRM Graphics, 61 Station Road, Winslow, Bucks

Made and printed in Spain by Printer industria grafica s.a., Sant Vicenc dels Horts, Barcelona
D.L.B. 1517O-1984

Line illustrations by Rob Shone
Original photography by Nick Wright

Except in the United States of America, this book is sold subject to the condition that it shall not, by way of trade or otherwise, be lent, re-sold, hired out, or otherwise circulated without the publisher's consent in any form of binding or cover other than that in which it is
published and without a similar condition including this condition being imposed on the subsequent purchaser





















INTRODUCTION

The BASIC assembler which is available on the BBC Microcomputer and Acorn Electron is a very powerful tool for programmers. It provides a comprehensible interface between the programmer and the machine code language which the 6502 processor itself uses. Hence the programmer is able to control the machine more directly using assembler.

The main reason why people write programs in assembler rather than BASIC is probably because of the speed difference between the two. Assembler instructions can be executed extremely quickly, a program written in BASIC will take between 10 and 100 times as long. Hence assembler is particularly useful for games' programmers since it enables them to move missiles and creatures across the screen quickly and smoothly. If BASIC was used to calculate the new coordinates of each object and draw them at those positions then movement would tend to occur in jerky leaps.

However, speed is not the only factor to be taken into consideration. Assembler programming gives you more power to solve a problem than BASIC does. All high-level languages require programs to have a certain structure and this puts constraints on programs written in that language.
Sceptics may advise against using assembler on the grounds that it is too complex. It is true that operations such as multiplication and division which are easy to perform in BASIC are not as straightforward in assembler. For what might be considered a trivial task, for example multiplying a number by three, several assembler instructions are required instead of just a single BASIC one. A further problem is that there are no FOR ... NEXT or REPEAT
UNTIL loops in assembler; if you require a loop you must set one up yourself. The same applies to floating point arithmetic assembler only supports integer calculations.

My advice is that you ignore these sceptics. It isn't difficult to learn to program in assembler. The programs look much less like English than BASIC I ones do but nevertheless to someone who knows the language they are easy to understand. Like learning to do anything else, all that is required is a certain amount of knowledge and a lot of practice. This book has been written to provide the knowledge - the rest is up to you.


The book is divided into three sections, each of which has a different task to perform. The first part aims to introduce the more useful assembler instructions available for the 6502 processor, giving simple examples of how they can be used. The second part introduces some of the more complex programming techniques which are aimed in particular at people writing large assembler programs. The third part is aimed mainly at the games' programmer. It provides many useful routines and finally shows how these can all be linked together to produce a complete game.









DATA STORAGE

Before you can start writing programs in assembler you need to know a few things about how data is stored inside the computer and how that data can be accessed and changed. This chapter looks at the ways in which you can enter numbers from the keyboard and the notation which the computer uses to store these values in its memorv.

~.1 Rexadecimal notation
To most people it seems natural to use base ten when dealing with numbers. We have ten digits; 0,1,... 8,9, and can use these to represent numbers as large as we please by making the value of a digit depend on which column it is in. Thus, when we consider the number 171 the first '1' represents 100, and the second '1' represents just one. Moving a digit one column to the left multiplies its value by ten; this is why our system is called base 10 or decimal.

When entering numbers into a computer you can still use base 10 if you wish, but another base - base 16 - is also available. For reasons which should become clear as you read through this chapter, base 16 (or hexadecimal) is far more suitable for working with computers. Hence it is advisable at this stage to spend some time becoming familiar with this number system.


In base 16 we need 16 different symbols to represent the 16 different 'hexadecimal digits'. For convenience we retain the symbols 0 to 9, and use the letters A to F to represent the values of ten to fifteen.

Hexadecimal 0 1 2 3 4 5 6 7 8 9 A B C D E F

Decimal 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Another difference between base 10 and base 16 is what happens to a digit or hexadecimal digit when it is shifted one column to the left. Whereas we have seen that in base 10 this multiplies the value of the digit by ten, in base 16 it multiplies the value by sixteen. Hence 10 in hexadecimal represents the value sixteen.

Having two bases in which we can work can lead to confusion. Consider, for example, the number 10; as we have seen this can represent either of the values ten or sixteen depending on whether it is being interpreted as a decimal or hexadecimal number. We need a method of specifying whether a number is decimal or hexadecimal. Normally we do this by prefixing hexadecimal numbers with an ampersand (&), e.g.

&B1

The 'B' has the value 16*11 because it is in the second column to the left, and the '1' represents 1 unit; the number therefore has the decimal value
176 +1 = 177.

&123

The '1' is in the third column to the left, so it hasthe value 16*16*1, the '2' has the value 16*2 and the '3' has the value 3. Adding these together produces
256 + 32 + 3 = 291

There is no real need to leam how to convert between hexadecimal and decimal because the computer can do it for you, as shown below.

Converting hexadecimal to decimal
To print out the decimal value of a hexadecimal number, such as &123, type

PRINT &123

The answer, 291, is printed out.

Converting decimal to hexadecimal

To print, in hexadecimal, the value of a decimal number, type
PRINT ~&123

The answer, 7B, is printed out. The number printed will be in hexadecimal notation, but note that the computer doesn't use the symbol '&' when it is printing hexadecimal numbers. In this case it is obvious that the answer is a hexadecimal number but for an answer such as 79 you would need to know which base you requested the computer to use to be able to interpret the result correctly.

The symbol twiddle or, more accurately, ti(de) ~ means 'print in hexadecimal'; thus writing


PRINT~123

will print 123.

  1. Binary notation and bits
Although the computer can accept numbers in either decimal or hexadecimal notation, it uses neither of these two systems for storing the numbers in its memory. The computer's memory consists of electronic circuits that can be put into one of two different states. The two states are normally represented as 0 and 1, but they are often referred to by different terms as listed below:

0 1
zero one
Low high
clear set
off on
false true
The circuits are said to be in a 'bistable state', i.e. they are always in one of two possible states. When the digits 0 and 1 are used to refer to these two states they are termed 'binary digits', or 'bits' for brevity.
With two bits, e.g. M and N, four different states can be represented:
M N
0 0
  1. 1
  2. 0
1 1

With a 'nibble', which is four bits, 16 different values can be represented (16 = 2^4). This means that a hexadecimal digit can be represented by a four-bit binary number. The hexadecimal digits and their binary equivalents are shown in the following table:
Decimal Hexadecimal Binary
0 0 0000
1 1 0001
2 2 0010
3 3 0011
4 4 0100
5 5 0101
6 6 0110
7 7 0111
8 8 1000
9 9 1001
10 A 1010
11 B 1011
12 C 1100
13 D 1101
14 E 1110
15 F 1111

Any hexadecimal number can be converted into its binary representation by the simple procedure of converting each hexadecimal digit into the corresponding four bits, for example

Hexadecimal &19
/ \
/ \
/ \
Binary 0001 1001


Thus the binary equivalent of &19 is 00011001, (or leaving out the leading zeros which are irrelevant,
11001).

1.3 Memory locations and bytes

The computer's memory is made up of a number of 'locations', each capable of holding a value. The size of each memory location is normally referred to as a 'byte'. Each byte can hold an eight-bit number, which means that it can store any one of 256 (2^8) different values; 0... 255.

We have seen already that each hexadecimal digit requires four bits to specify it. A byte, since it contains eight bits, can therefore represent any hexadecimal number between 0 and &FF.

The bits in a byte are usually numbered for convenience, as follows:

bit number 7 6 5 4 3 2 1 0
byte 0 0 0 1 1 0 0 1


Bit 0 is often referred to as the 'low-order bit' or 'least-significant bit', and bit 7 as the 'high-order bit' or most-significant bit'.

1.4 More about memory locations

Somehow it must be possible to distinguish between one location and another. Houses in a town are distinguished by each of them having a unique address. Even when the occupants of a house change, the address of the house remains the same. Similarly, each location in a computer has a unique 'address' consisting of a number which remains unchanged even when the contents of the memory location are altered. Thus we can speak of the 'contents of location 100' as being the number found in the location whose address is 100. The memory locations start from address 0 and could look something like this:

Decimal value of the number being stored
27 35 6 91
Address 0 1 2 3

An address can be one or two bytes long. This means that addresses can cover the range 0 to &FFFF. For a detailed look at which part of the memory each address corresponds to see the memory maps in Appendix A.

Examining memory locations

We can look at the contents of some memory locations in the computer using the query (?) operator. The reference '?X' means use the value of X as the address of the location under consideration. Hence the reference '?&FFEE' means that we are concerned with the location whose address is &FFEE. To look at this location type

PRINT ?&FFEE

This prints out the value found at the location specified, which in this case should be the number 108. Any memory location can be examined in this way and all of them will contain a number between 0 and 255.

It is often convenient to look at several memory locations in a row; for example, to list the contents of the 32 memory locations from &70 upwards, type
FOR N = 0 TO 31 : PRINT ?(N+&70); :NEXT N

An alternative way of writing this is

FOR N=0 TO 31 : PRINT N?&70; : NEXT N
This method is tidier than the other and gives

identical results; i.e. for each of the values of N between 0 and 31, N is added to the number &70 to give the address of the location whose contents are to be printed out. This should result in the contents of 32 memory locations being listed on the screen.




Changing memory locations

It is possible to change the number stored at a particular memory location by assigning a new value to it. As an example try changing the contents of &70. First, print the contents of this address; the value there will be whatever was in the memory when the computer was switched on since the computer does not use this location for storing any of the variables it is working with. To change the contents to 7, type

?&70=7

To verify the change, type

PRINT ?&70

Try setting the contents of this memory location to other numbers. Setting the contents to a number greater than 255 or &FF will result in the number entered modulo 256 being stored there, for example

? &70=600
PRINT ?&70

This will print out

(600 MOD 256)

A word of warning: Before you change the contents of any other memory locations be sure that vou know what you are doing. Although it is quite safe to look at almost any memory location in the computer, care must be exercised when changing any of them. The example given here uses a specific location which is not used by the computer; if you change any other location you may lose any program you have in memory or confuse the computer to such an extent that it proves necessary to reset it by pressing BREAK to make it accept any further commands.

1.5 Negative numbers - two's complement

Although the values stored in the memory locations are between 0 and 255 these can be used to represent both positive and negative numbers. To do this two's complement representation is used. To represent a number using this system we first have to consider what its positive counterpart is in binary notation. For example to find out how -5 would be stored consider the number
+5 = 00000101
We then find the complement of this, i.e. change each 0 into a 1 and each I into a 0, e.g.

complement of +5 = 1111010

Finally we add one: 11111010
+
1
11111011

This gives us the two's complement representation of -5.

We can now try adding together +5 and -5 to see if they give us 0.
00000101
11111011
(1) 00000000

Ignoring the 1 which has overflowed gives us the result, zero, which we were expecting.

Note that when representing numbers using two's complement notation a single byte can represent any number between -128 and +127. The left-hand bit is 1 if the number is negative and 0 otherwise. Zero is classed as a non-negative number.

1.6 Storing text

If locations can only hold numbers between 0 and
255, how is text stored in the computer's memory?
The answer is that numbers are used to represent thedifferent characters. Hence text is stored simply as a sequence of numbers in successive memory locations. The computer does not become confused about whether a number is representing an actual number or a character since the context will always make it clear how it should be interpreted.

The unique number corresponding to each character is given by its ASCII code (American Standard Code for Information Interchange). To find the ASCII code of a given character the ASC function can be used, for example type

PRINT ASC “A

and the number 65 will be printed out. This means that the character 'A' is represented internally by the number 65. If you try repeating this process for B C D you will notice that there is a certain regularity. The same is true for a b c~... and the sequence 1 2 3 4

A full table of the ASCII codes used to represent all the characters is given in Appendix A.





















2 CARRYING OUT INSTRUCTIONS

The previous chapter showed how characters and numbers are represented in the computer's memory, i.e. how the computer deals with data storage. However, data on its own is useless to a computer, it also needs instructions telling it what to do with the data. This chapter looks at how the computer handles instructions and explains some of the simpler assembler instructions which it can use. The instructions listed refer only to the 6502 processor so if you have any other sort of processor connected to your machine, e.g. a Z-80 second processor, this will have to be disabled before attempting any of the routines given in this and subsequent chapters.

2.1The CPU
The Central Processing Unit or CPU is the computer's brain. It is the most active part of the computer; although areas of memory can remain unchanged for hours on end when a computer is being used, the CPU is working all the time the machine is switched on. The CPU's job is to read a sequence of instructions from memory and carry out the operations specified by those instructions.

The instructions which the CPU acts on are just values stored in memory locations. The CPU takes a byte and interprets it as an instruction, e.g. &18 will be interpreted to mean 'clear carry flag'; this will be explained later in this chapter. It then performs the operation as instructed and goes on to collect the next byte.


The CPU, the computer's brain.
The first byte of all instructions is the operation code, or 'op-code'. Some instructions, such as the example above, consist of just the op-code; other instructions require data on which they must operate. These instructions therefore consist of two or three bytes, the first one being the op-code and the other one or two consisting of data. For example the value &E6 is translated into the instruction 'increase the contents of the memory location with the following one-byte address by one'. Hence the CPU then takes the next byte from the memory and interprets this, not as an instruction, but as the address of the location whose contents are to be incremented. It then adds one to the number stored in that location. Having executed this instruction, the CPU then goes on to the next byte which is taken to represent the next instruction it must perform.

2.2Machine code V assembler
The above few paragraphs should have given you the idea that everything the CPU acts upon is a number between 0 and &FF (255 decimal); each number being interpreted by the CPU as an instruction or some data which an instruction must use. The list of numbers which are being used are referred to as machine code. It is possible for us to talk to the computer in its own language, i.e. program in machine code, but this would mean that we would have to know which instructions all the op-codes stand for. Programming in assembler alleviates the need for learning all these translations. In assembler each op-code is represented by a three letter mnemonic, e.g. CLC is used instead of &18 to give the instruction 'clear carry'. The computer then converts all the mnenomics into the corresponding op-codes.

This process is carried out by an assembler and hence is known as 'assembling'. The program that the assembler takes as its input is known as the source code, and the machine code output is referred to as the object code.

2.3 The accumulator and the carry flag
The accumulator is just a temporary location inside the CPU which plays a part in many of the operations performed by the CPU. For example, to add two numbers together you have to load the first number into the accumulator from the memory, add in the second number from memory, and then store the result somewhere. To do this the following assembler instructions will be needed:

Mnemonic Description Symbol

LDA load accumulator from A=M
memory
STA store accumulator in M=A
memory
ADC add memory to A=A+M+C
accumulator with carry
CLC clear carry C=0

The carry is needed to allow numbers greater than one byte (255 or &FF) to be generated. When an eight-bit value is added to another eight-bit value the result could be too great to be represented by eight bits, e.g. 140 +160 = 300 (>255).

In order to allow for this, the CPU will use the carry as the ninth bit of the accumulator, and thus the carry will contain the extra bit. In the above example, when the numbers 140 and 160 are added together and the result stored in a memorv location, this location will contain the value 44 (300 MOD 256). By using the carry flag you will have a record of whether the result of the addition was actually the value 44 or if it was 300. Hence, to avoid confusion, clear the carry before performing any additions.

2.4 Writing an assembler program
Enter the following assembler program:

10 DIM P% 100
20[
30 LDA &80
40 CLC
50 ADC &81
60 STA &82
70 RTS
80]
90 END

The meaning of each line in this assembler program is as follows:

10 The DIM statement is not an assembler mnemonic; it is a BASIC instruction to tell the assembler where to put the assembled machine code by DIMensioning off an area of memory for it. The DIM statement is followed by a number (not in brackets) and the statement reserves this number of bytes for the machine code which will be generated. As a rough guide to the amount of room needed count the number of assembler instructions used, treble it and reserve at least this number of bytes.

The BASIC variable P% is used by the assembler as a location counter to specify the next free address. Hence the statement sets P0/o to the lowest address of the reserved block of memory and then as each byte of machine code is generated, P0/o increases by one byte so that it always points to the next free location.

20 The '[' symbol is an 'assembler delimiter' which has to be used immediately before the first assembler statement to tell the BASIC interpreter that the following statements will be in assembler rather than BASIC.

30 Load the accumulator with the contents of the memory location whose address is &80. (The contents of the memory location are not changed.)

40 Clear the carry flag.

50 Add the contents of location &81 to the accumulator with the carry. (Location &81 is not changed by this operation.)

60 Store the contents of the accumulator to location &82. (The accumulator is not changed by this operation.)

70 The RTS instruction will usually be the last instruction of any program; it causes a return to BASIC from the machine-code program. The mnenomic stands for 'return from subroutine'.

80 The ']' symbol is an assembler delimiter which has to be used after the last assembler instruction to tell the interpreter that the following statements will be in BASIC.

90 The END statement is not an assembler mnemonic; it just denotes the end of the program.

Now type RUN and the assembler program will be assembled; the assembled code being inserted directly in memory at the address specified by P0/o.
An 'assembler listing' will be printed out to show the machine code the assembler has generated to the left of the corresponding assembler mnemonics:

>RUN
OE5D
OE5D A5 80 LDA &80
OE5F 18 CLC
0E60 65 81 ADC &81
0E62 85 82 STA &82
0E64 60 RTS
operand
mnemonic statement
instruction data/address
instruction op code
location counter statement

The program has been assembled in memory starting at &0E5D, immediately after the program text. This address may be different when you enter the example program if you have inserted extra spaces into the program or if you have filing systems other than cassette in your machine, but that will not affect any other part of the listing. All the numbers in the listing are in hexadecimal; thus &18 is the op-code for the CLC instruction, and &A5 is the op-code for LDA when the number being loaded is not given directly but is obtained by looking in the memory location whose one-byte address is given. Hence this LDA instruction consists of two bytes; the first byte is the op-code, and the second byte is the address; &80 in this case.

Another method of finding out where the machine code is, is to find out where 'TOP' is by typing

PRINT ~TOP

This value gives the address of the memory location immediately after the program text. Since the machine code follows on straight after the text this address is the one corresponding to the first instruction, &A5. Thus the machine code is stored in memory as follows:

A5 80 18 65 81 85 82 60
^
TOP

When 'RUN' was typed this assembled the assembler program and put the machine code produced into the computer's memory, however it did not execute the program. The method for doing this is described below.

2.5 Executing a machine-code program

To execute the machine-code program at TOP, type

CALL TOP

Nothing obvious will happen except for the '>' prompt being printed again on the screen. This indicates that the computer has finished executing the program and hence the contents of locations &80 and &81 will have been added together and the results placed in &82.

You can verify this by setting the contents of &80 and &81 to certain values by typing, for example

?&80=7 : ?&81=9

If you wish you can also set the contents of &82 to 0. Now type

CALL TOP

and then look at the contents of &82 by typing

PRINT ?&82

The result is 16 (in decimal); the computer has just added 7 and 9 and obtained 16

2.6 Adding two-byte numbers

Try executing the program for different numbers in
&80 and &81. You might like to try the following:

?&80=140 : ?&81=160 CALL TOP

We saw earlier in this chapter that if an addition generates a number greater than 255 then the result stored in the memory location specified will be that number modulo 256. Hence the result in this case will be 44 rather than 300. Here is the calculation in hexadecimal:
160 &A0
140 &8C
300 &12C

Only two hex digits can fit in one byte, so the '1' of &12C is lost, and only the &2C is retained. Luckily the '1' carry is retained for us in the carry flag as was mentioned earlier, though we didn't see then how to use this. The example below shows how the two numbers can be treated as being two-byte numbers and added together using the carry to produce a two-byte number which is the complete answer. This method can be extended to any number of bytes since the carry flag makes it a simple matter to add together two numbers as large as we please. Modify the program already in memory by retyping lines 50 to 120, if you wish (leaving out the comments to the right of the assembler text). Here is the modified program:

10 DIM P% 100
20[
30 LDA &80 Low byte of one number
40 CLC Clear carry flag
50 ADC &82 low byte of other number
60 STA &84 low byte of result
70 LDA &81 high byte of one number
80 ADC &83 high byte of other number
90 STA &85 high byte of result
100 RTS
110]
123 END

Assemble the program:

>RUN
OE6E
OE6E AS 80 LDA &80
0E70 18 CLC
0E71 65 82 ADC &82
0E73 85 84 STA &84
0E75 AS 81 LDA &81
0E77 65 83 ADC &83
0E79 85 85 STA &85
OE7B 60 RTS

Now set up the two numbers as follows:

?&81=&8C : ?&81=&00
?&82=&A0 : ?&83=&00

Finally, execute the program by typing

CALL TOP

and look at the result, printing it in hexadecimal this time for convenience:

PRINT ~?&84, ~?&85
The low byte of the result is &2C, as was obtained

before using the one-byte addition program, but this time the high byte of the result, &1, has been correctly obtained. The carry generated by the first addition was added into the second addition, giving

0+ 0 + carry = 1

Try some other two-byte additions using the new program.

2.7 Subtraction

The subtract instruction is just like the add instruction, except that there is a 'borrow' if the carry flag is zero. Therefore to perform a single-byte subtraction the carry flag should first be set with the SEC instruction.

Mnemonic
SEC set carry flag C=1
SBC subtract memory from A=A-M-(1-C)
A with carry

Example

10 DIM P% 100
20[
30 LDA &80 Low byte of first number
40 SEC Initialise carry flag
50 SBC &82 Low byte of other number
60 STA &84 Low byte of result
70 LDA &81 Now do high bytes
80 SBC &83
90 STA &85
100 RTS Return
110]
120 END

Note that the above program is very similar in structure to the addition example in section 2.6.

2.8 Comments
There are two methods of putting comments in assembler programs. The first of these, which is used in previous examples, is to put the comment after an assembler instruction, separated from it by one or more spaces, e.g.

60 STA&84 low byte of result

Alternatively a statement may start with a backslash (\), in which case the remainder of that statement is ignored, e.g.

65 Now for the high bytes

Note that a colon (:) will end the comment and start a new assembler statement, for example line 60 could be replaced by

60 Low byte of result STA &84

2.9 Printing a character
The computer contains routines for the basic operations of printing a character to the VDU, and reading a character from the keyboard, and these routines can
be called from assembler programs.
Name Address Function
OSWRCH &FFEE Puts character in accumulator
to output (VDU)
OSRDCH &FFEO Reads from input (keyboard)
into accumulator

In each case all the other registers are preserved. The names of these routines are acronyms for ‘operating system write character' and 'operating system read character' respectively. These routines are executed with the instruction JSR (jump to subroutine).

A detailed description of how the JSR instruction works will be left until the following chapter.

The following program outputs the contents of memory location &80 as a character to the VDU, using a call to the subroutine OSWRCH:

10 DIM P% 100
20 oswrch=&FFEE
30[
40 LDA &80
50 JSR oswrch
60 RTS
70]
80 END

The variable 'oswrch' is used for the address of the OSWRCH routine. Assemble the program, and then set the contents of &80 to &21 by typing

?&80=&21

Then execute the program using

CALL TOP

and an exclamation mark will be printed out before returning to the computer's prompt character, because &21 is the code for an exclamation mark. An alternative method of setting the contents of location &80 to &21 is therefore

?&80=ASC"!"

Try executing the program with different values in &80, with values chosen from the table of ASCII values in Appendix A.

2.10 immdiate addressing
In the previous example the instruction

LDA &80

loaded the accumulator from the location whose address is &80, this is known as 'absolute' addressing. The location was then set to contain &21, the code for an exclamation mark. If at the time the program was written it was known that an exclamation mark was to be printed in would be more convenient to specify this in the program as the actual data to be loaded into the accumulator. Fortunately an 'immediate' addressing mode is provided which achieves just this. Change the instruction to

LDA #&21

where the '#' (hash) symbol specifies to the assembler that immediate addressing is required. Assemble the program again, and note that the instruction op-code for 'LDA #&21' is &A9, not &A5 as it was previously for the absolute addressing. The op-code of the instruction specifies to the CPU whether the following byte is the actual data loaded, or the address of the location containing the data.

2.11 Using addresses
So far when a value has been saved, a numerical address has been used to define where it is to be stored, e.g.

STA &80

A better method of giving an address is to use a variable name, e.g.

STA addr

In this case 'addr' must be specified at the beginning of the program, e.g.

addr = &80

This method is better than the previous one since it makes the program easier to understand, i.e. an address can be given a relevant name, e.g. 'xlowbyte' or 'yhighbyte'. In addition, changing the location of a value becomes easier, since only the initial specification need be altered rather than every occurence of that value throughout the program.

The locations used must be chosen carefully to avoid corrupting operating system or BASIC workspace. The memory map in Appendix A should help to show which locations can be used in different circumstances. Also there are some locations which are always free when using BASIC; these are &70 to &8F.

3 JUMPS,BRANCHES AND LOOPS

When an assembler program has been assembled and is being executed, the address of the next instruction to be executed is kept in a register called the 'program counter'. All the programs met so far have been executed in the order that the instructions were written, so the program counter has just steadily increased until it reached the last instruction. This chapter introduces the jump and branch instructions which can make the program counter jump over instructions or move back to previous ones to execute them again. These instructions make it possible to implement loops and perform different instructions depending on the outcome of previous ones.

3.1 Jumps
Ordinary jumps
Mnemonic Description
JMP jump to instruction whose address is given

The JMP instruction is followed by the address of the instruction to be executed next, e.g.
JMP &E48 or JUMP addr

Instead of describing the address by a number, we can use a 'label' to indicate to the assembler where we want to go. In the assembler, labels are variables prefixed with a full stop (.).

10 oswrch =&FFEE
20 DIM P% 100
30[
40.enter
50 LDA #ASC”*”
60..Loop
70 JSR oswrch
80 JMP Loop
90]
100 END

When the program is assembled the address corresponding to the label '.loop' will be inserted in the machine code. When the code is executed the value of the program counter will be set to this address and the CPU will collect its next instruction from the location with that address and will continue executing from there.

The label '.enter' at the start of the program has been included so that this label can be called in order to execute the program. This is a better way of executing a program than calling TOP since TOP doesn't always point to the first machine code instruction. This is true for the above example since TOP will point to the assignment statement ‘oswrch=&FFEE'.

The program will output an asterisk (*), and then jump back to the previous instruction. The program has become stuck in an endless loop! Compare this program with the following BASIC program:

10 A
20 VDU A
30 GOTO 20

To get out of the BASIC loop you press ESCAPE. This will not automatically halt machine code programs, however. To exit from a machine code loop without losing the program you must press BREAK, and then type 'OLD' to retrieve the original program.

Jumps to subroutines

Mnemonic Description
JSR jump to subroutine

Examples of this instruction have been used previously. Like the JMP instruction it is followed by a two-byte address, e.g.

JSR oswrch

In this case the address of the instruction directly following the JSR instruction in the code is noted, and then the value of the program counter is set to the address of 'oswrch'. The CPU will go to this address for its next instruction and start executing the code from there until it meets an RTS. This will set the program counter to the address which was noted earlier so that the CPU can then continue executing the code following the JSR instruction. Subroutines jumped to can either be part of the assembler program or, as in this example, sub-routines which exist in the operating system memory.

3.2 The zero and negative flags
There are several flags in the CPU which can be set or cleared depending on the outcome of certain instructions. The carry flag was introduced in the previous chapter, this is set or cleared as the result of an ADC (add with carry) instruction. Another very useful one is the zero flag, called Z. This is set if the result of the previous operation gave zero, and is cleared otherwise, e.g.

LDA &8O

would set the zero flag if the contents of &80 were zero.

Similarly the negative flag, N, is set if the result of the previous operation was negative in two's complement notation, i.e. if the top bit was set, e.g.

LDA &80

would set the negative flag if the number stored in location &80 was greater than 127 (01111111).

The conditions of all the flags are stored in a byte called the status register (P), and each flag is represented by one bit: e.g. the top bit of the status register is set if N=1 and the bottom bit is set if C=I.

3.3 Conditional branches
Conditional branches enable the program to act on the outcome of an operation. There are eight different branch instructions, six of which are introduced.

Mnemonic Description Status
BEQ branch if equal to zero (ie Z=1)
BNE branch if not equal to zero (ie Z=0)
BCC branch if carry clear (ie C=0)
BCS branch if carry set (ie C=1)
BPL branch if plus (ie N=0)
BMI branch if minus (ie N=1)

The conditional branch instructions test the state of the various condition flags, e.g. the zero flag and negative flag. If the condition is not satisfied then it carries on executing, but if the condition is satisfied then the computer goes to the place indicated by the byte following the branch op-code. This byte is stored as a relative address, thus if you say

BCS notzero

the assembler works out the difference (in bytes) between the current instruction and the place where the label '.notzero' is, and puts this value after the op-code. This means that the value of this byte is used, in conjunction with the address of the current instruction, to tell the CPU where to go next.

Because only a single byte is allowed in this relative addressing mode, the branch instructions can only point to one of 255 nearby bytes. The two's complement representation of numbers is used to give the offset relative to the current address. Branches which point forwards are restricted to 0-127 bytes beyond the current location. The value of the byte following the op-code for these is then 0-127. Branches which point backwards to places at lower addresses in memory require a negative value to be added to the current location. These use the numbers 128-255 to represent the values -128 to -1.

The JMP instruction does not use relative addressing; it is followed by two bytes which specify the absolute address which will be the destination. Hence the branch instruction is shorter than the jump instruction, the jump being three bytes long (op-code and two-byte address) and the branch being two bytes long (op-code and one-byte offset). This difference is automatically looked after by the assembler.

The following simple program will print an

exclamation mark if 'character' contains zero, and a star if it does not. The comments to the right of the assembler statements may be omitted when you enter the program.

10 DIM P% 100
20 character=&80
30 oswrch = &FFEE
40[
50.enter
60 LDA character
70 BEQ exclamation If zero print '!’
80 LDA #ASC"*" Star
90 JSR oswrch Print it
100 RTS Return
1 10.exc Lamat ion
120 LDA #ASC"!" ExcLamation mark
130 JSR oswrch Print it
140 RTS Return
150]
160 END

Note that the above program can be made shorter, by replacing the instructions

JSR oswrch
RTS

with the single instruction

JMP oswrch

Replacing JSR and RTS instructions by a JMP to a subroutine reduces the size of both a source program and the object code it produces, and hence increases execution speed.

Now assemble the program by typing RUN. You should get the message:

No such variabLe at Line 70

This is because the assembler processes the mnemonic instructions in the order in which they are listed in the program. Therefore when it encounters 'BEQ exclamation' it has not yet found the label exclamation' so it cannot work out the offset which is required in the following byte. This is known as the forward-reference problem, and is easily overcome using the method of two-pass assembly which is explained below.

3.4 Two-pass assembly
When a program contains forward references it needs to be assembled twice. During the first pass of the assembler the addresses of all the labels are noted so that during the second pass the offsets of the branch instructions can be included. And the assembler must be told not to worry when, during the first pass, it comes across errors of the sort indicated above.

This can be done using the OPT statement, an assembler directive which has a single parameter for which the following values are possible:

OPT 0 No error messages, and no listing
OPT 1 No error messages, and listing
OPT 2 Error messages reported, and no listing
OPT 3 Error messages reported, and listing (Default)

Thus to suppress messages and a listing on the first pass, and to restore them on the second pass, we need to use OPT 0 and OPT 3 respectively. This can be effected by placing the directive inside a FOR NEXT loop, which goes from 0 to 3 in steps of 3. Then

The value of the control variable is used as the parameter of the OPT statement. So, to alter the program which was given above, simply enter these lines:

10 DIM code 100
23 FOR pass = 0 TO 3 STEP 3
26 P% = code
30[ OPT pass
145 NEXT pass

This time the error message will not be produced and the correct offset will be calculated for the branch instruction.

Note lines 10 and 26, which replace the old 'DIM P% 100' statement. P% must be reset to the starting value each time that the code is assembled.

Now execute the program by typing

CALL enter

and verify that the program behaves as it should for different values in &80.

  1. XandYregisters

The CPU contains two registers, called the X and Y registers, in addition to the accumulator. As with the accumulator, there are instructions to load and store the X and Y registers:
Mnemonic Description Symbol

LDX load X register from memory X=M
LDY load Y register from memory Y=M
STX store X register to memory M=X
STY store Y register to memory M=Y

However, unlike the accumulator, the X and Y registers cannot be used as one of the operands in arithmetic instructions; they have their own special uses which will be outlined later.

The X and Y registers are particularly useful as the control variables in iterative loops, because four special instructions exist which will either increment (add 1 to) or decrement (subtract 1 from) their values.
Mnemonic Description Symbol

INX increment X register X""X+1
INY increment Y register Y=Y+1
DEX decrement X register X""'X-1
DEY decrement Y register Y=Y-1

Note that these instructions do not affect the carry flag: incrementing &FF will give &00 without changing the carry bit. The zero and negative flags are, however, affected by these instructions.

  1. Iterative loops

The iterative loop enables the same set of instructions to be executed a fixed number of times, e.g.

10 DIM P% 100
20 oswrch = &FFEE
30E
40.enter
50 LDX #8 InitiaLise X
60 LDA #ASC"*" Code for star
70. Loop
80 JSR oswrch Output star
90 DEX Count it
100 BNE Loop ALL done?
110 RTS
120 END

Assemble the program by typing RUN. This program prints out a star, decrements the X register, and then branches back if the result after decrementing the X register is not zero. Consider what value X will have on successive trips around the loop and predict how many stars will be printed out; then execute the program with 'CALL enter' and see if your prediction was correct. (If you were wrong, try thinking about the case where X was initially set to 1 instead of 8 in line 50.)

How many stars are printed if you change the instruction on line 50 to 'LDX #0'?

3.7 Comparing values
In the previous example the condition X=0 was used to terminate the loop. Sometimes we might want to count up from 0 and terminate on some other specified value. The compare instruction can be used to compare the contents of a register with a value in memory; if the two are the same, the zero flag will be set. If they are not the same, the zero flag will be cleared. The compare instruction also affects the carry flag by setting it to 1 if the register is greater than or equal to the value in memory, and 0 otherwise.

Mnemonic Description Symbol

CMP compare accumulator with A-M
memory
CPX compare X register with X-M
memory
CPY compare Y register with Y-M
memorv

Note that the compare instruction does not affect its two operands, it just changes the flags as a result of the comparison.

The next example again prints eight stars, but this time it uses X as a counter to count upwards from 0 to 8.

10 DIM P% 100
20 oswrch=&FFEE
30[
40.enter
50 LDX #0 Start at zero
60..Loop
70 LDA #ASC”*” Code for star
80 JSR oswrch Output star
90 INX Next X
100 CPX #8 ALL done?
110 BNE Loop If not then repeat
120 RTS Else return
130]
140 END

In this program X takes the values 0, 1, 2, 3, 4, 5, 6, and 7. The last time around the loop X is incremented to 8, and the loop terminates. Try drawing a flowchart for this program.

3.8 Using the control variable
In the previous two examples X was simply used as a counter, and so it made no difference whether we counted up or down. However, it is often useful to use the value of the control variable in the program. For example, we could print out the character in the X register each time around the loop. The order in which we want the characters would then determine whether we count up or down. We therefore need a way of transferring the value in the X register to the accumulator so that it can be printed out by the OSWRCH routine. One way would be to execute:

STX tempaddr
LDA tempaddr

where 'tempaddr' is not being used for any other purpose. However, there is a more convenient way, using one of four new instructions:

Mnemonic Description Symbol

TAX transfer accumulator to X register X=A
TAY transfer accumulator to Y register Y=A
TXA transfer X register to accumulator A=X
TYA transfer Y register to accumulator A=Y

Note that the transfer instructions only affect the register being transferred to.

The following example prints out the alphabet by making X cover the range A to Z.

10 DIM P% 100
20 oswrch = &FFEE
30[
40.enter
50 LDX #ASC"A" Start with the Letter A
60.Loop
70 TXA Put it in the accumuLator
80 JSR oswrch Print it
90 INX Next one
100 CPX # (ASC"Z”+1) Finished ?
110 BNE Loop If so - continue
120 RTS ELse return
130]
140 END

All these examples could have used Y as the control variable instead of X in exactly the same way.

3.9 Conditional assembly
Assembler source text can contain tests, and assemble different statements depending on the outcome of these tests. This is especially useful where slightly
Different versions of a program are needed for many different purposes. Rather than creating a different source file for each different version, a single variable can determine the changes using conditional assembly, e.g

10 DIM CODE%100
20 char=&70
30 oswrch=&F FEE
40 osrdch=&FFEO
50 beLL=7 Bleep = VDU 9
60 prompt=ASC":”
70 INPUT"bell",beLL$ Input 'bell$'
80 bellflag=INSTR("Yy",bell$) bellflag' is true if
90 FOR pass=0 TO 3 STEP 3 bell$ = 'y' or 'Y'
100 P%=CODE%
1 10[ OPTpass
120.enter
130]
140 IF bellflag THEN [OPT pass:LDA #bell :JSR oswrch:]
150[OPT pass
160 LDA #prompt Load A with ':' prompt
170 JSR oswrch Print from A
180 JSR osrdch Read in a character
190 STA char store it at char and
200 JSR oswrch ,print it out
210 RTS
220]
230 NEXTpass

When this program is run it asks if you want the computer to bleep or not and sets 'bellflag' accordingly. Then when the machine code is executed it inputs a character from the keyboard, bleeping if 'bellflag' is set to remind you that an input is required, and prints out a character corresponding to the first key pressed. This character is also saved at the address 'char'.

4LOGICAL OPERATIONS, SHIFTS AND ROTATES


We have seen previously that each byte or memory location is made up of eight bits, each of which can be set to the value 0 or 1. Although the operations we have considered so far have treated the whole byte as the smallest quantity being dealt with, many operations in the computer's instruction set are best considered as operations which act on eight separate bits. Some of these perform such important tasks as changing the case of characters, or multiplying and dividing.

4.1 Logical operations
Logical operations are performed between the individual bits of two operands; one of the operands is always the accumulator and the other is a memory location or immediate value. In this section three such operations are introduced; AND, OR and EOR. A truth table is used to give a compact description of each operation. This takes two single bit inputs which, for convenience, we call A and B, and shows the bit which is produced as a result of ANDing, ORing or FORing them together. This is known as Boolean logic after its inventor, George Boole.

AND
Mnemonic Description Symbol
AND AND accumulator with memory A=A AND M

Truth table:

A
B
out
0
0
0
0
1
0
1
0
0
1
1
1

The AND operation sets a bit of the result to a 1
only if the corresponding bit of one operand is a 1
AND the corresponding bit of the other operand is a
1; otherwise the bit in the result is a zero, e.g.

Hexadecimal Binary
operand 1 A9 10101001
operand 2 E5 11100101
result of AND Al 10100001


One way of thinking of the AND operation is that one operand acts as a 'mask', and only where there are ones in the mask do the corresponding bits in the other operand 'show through'; otherwise, the bits are zero.

OR

Mnemonic Description Symbol
ORA OR accumulator with A=A OR M
memory

Truth table:
A
B
out
0
0
0
0
1
1
1
0
1
1
1
1

The OR operation sets a bit of the result to a I if the corresponding bit of one operand is a 1 OR the corresponding bit of the other operand is a 1, or indeed, if they are both ones; otherwise the bit in the result is zero, e.g.

HexadecimaL Binary
operand 1 A9 10101001
operand 2 E5 11100101
Result of OR ED 11101101


Exclusive OR
Mnemonic Description Symbol
EOR Exclusive-OR accumulator A=A EOR M
with memory

Truth table:
A
B
out
0
0
0
0
1
1
1
0
1
1
1
0


The Exclusive-OR operation is like the OR operation, except that a bit in the result is set to I only if the corresponding bit of one operand is a 1, or if the corresponding bit of the other operand is a I, but not if they are both ones, e.g.

HexadecimaL Binary
operand 1 A9 10101001
operand 2 E5 11100101
result of EOR 4C 01001100


Another way of thinking of the Exclusive-OR operation is that a bit of the result is 1 if and only if the corresponding bits in the operands are different.

Example - converting lower to upper case
The following example converts all characters entered in lower case to upper case. See Appendix A for the ASCII character set.
.Loop
JSR osrdch Get character
AND #&DF Make case bit zero
JSR oswrch Print it
JMP Loop And do it again

Try altering this using the 'ORA' instruction to convert all characters to lower case. When you have succeeded in doing this try writing a routine to swap case.

  1. The BIT instruction

This instruction is available to test whether individual bits of a number are set or not.

Mnemonic Description
BIT Compare memory bits with accumulator

The instruction AND's the bits of the accumulator and the memory. The zero and negative flags are set or cleared as a result of this operation; Z=1 if the result was 0 and N = top bit, V = bit 6 of contents of location.

Hence BIT may be used to test any bit of the memory by loading the accumulator with a value containing a 1 in the relevant position and 0's everywhere else. Then the values 0 and 1 for Z show whether the bit was or was not set respectively, e.g.

LDA #4 4=00000100
BIT addr
BEQ bit-not-set

If addr contained, for example, &43 (01000011) then the branch would occur. If, however, addr contained &44 (01000100) then the branch would not take place. Bit differs from the AND instruction in that it does not corrupt the accumulator.

4.3 Rotates and shifts
The rotate and shift operations move the bits in a byte either left or right.

Mnemonic Description
ASL arithmetic shift left
ROL rotate left
LSR logical shift right
ROR rotate right

The ASL instruction moves all the bits one place to the left; what was the high-order bit is put into the carry flag, and a zero is put into the low-order bit of the byte. The ROL instruction is identical except that the previous value of the carry flag is put into the low-order bit instead of zero.

The right shift and rotate right instructions work in a similar way except that the bits are shifted to the right.

ASL - Arithmetic
shift Left one bit

ROL - Rotate Left one bit

LSR - Logical shift
right one bit

Example - multiplying by two

The most efficient way to multiply a two-byte number, stored in 'addr' and 'addr+1, by two, is to shift the contents of the two bytes one place to the left. Where 'addr' and 'addr+1' are the addresses of the locations storing the low and high bytes of the number being doubled, use the following two statements:

ASL addr
ROL addr + 1

This works by using the carry to hold the bit that falls off the end of 'addr', and then using the ROL statement, which puts the carry into the correct place in the high-order byte.


5 ADDRESSING MODES

So far we have met two addressing modes. One of these is absolute addressing as in

LDA addr

which, when executed, loads the accumulator with the contents of the location whose address is 'addr'. The other is immediate addressing as in

LDA #&81

which, when executed, loads the accumulator with the actual value &81.

However, other addressing modes exist and one of the most important, 'indexed addressing', is introduced here prior to a summary exposition of all the addressing modes available to the 6502 processor.

  1. Indexed addressing

In this addressing mode one of the index registers (X or Y) is added to the address as an offset which gives the precise location for the stored data. For example, we can write:

LDA addr, X

If X contains zero this instruction will behave just like 'LDA addr'. However, if X contains 1 it will load the accumulator with the contents of 'one location further on from addr'. Since X can contain any value from 0 to 255, the instruction 'LDA addr,X' gives you access to 256 different memory locations. If you are familiar with BASIC's byte vectors you can think of 'addr' as the base of a vector, and of X as containing the subscript, e.g.

addr?7 = 12

is equivalent to

LDA #12
LDX #7
STA addr, X

  1. String types

Two examples of the use of indexed addressing are given below, both involving strings. There are two string types available for use in BASIC and assembler; ATOM strings and Microsoft strings. An ATOM string is a string of characters terminated by a RETURN character. The name which identifies the string is preceded by a dollar ($) sign and the strings can be easily set up in BASIC, e.g.

$name = "Fred"

ATOM strings must have an area of memory set aside for them. This can be done, as in the examples, by using a DIM statement. The characters making up the string are then stored in the location identified by the name of the string. This is very useful as the address of each character is then also known.

A Microsoft string is a string of characters preceded by a byte which gives the length of the string. In this case, the name of the string has a dollar ($) sign after it. It is more flexible than the ATOM string because it can contain RETURN characters. Its disadvantage is that all the characters making up the string are stored in locations chosen by BASIC, hence the addresses of these are not known.

Example - print inverted-case string
The following program uses indexed addressing to

print out a string of characters terminated by a carriage return (which is represented in the memory by &D), swapping case as it prints out each character.

10 DIM string 256, code 100
20 oswrch = &FFEE
30 FOR pass = 0 TO 3
40 P% = code
50[OPT pass
60.enter
70 LDX #0 Set index to zero
80. Loop
90 LDA string,X Get characters from string
100 CMP #&D Is it end of string ?
110 BEQ return If so, end
120 EOR #&20 Else invert case bit
130 JSR oswrch Print it
140 INX Increment index
150 BNE Loop If string Longer than 256
60..return
170 RTS then end anyway
180]
190 NEXT pass
200 END

Assemble the program by typing RUN, and then try the program by entering:

$string = "Test String" CALL enter

Example index subroutine

Another useful operation, easily performed in a machine-code routine, is looking up a character in a string and returning its position in that string. The following subroutine reads in a character, using a call to the OSRDCH read-character routine, and saves in '? found' the position of the first occurrence of that character in '$target'. This is exactly the same as the BASIC '?found =INSTR("ABCDEFGH",GET$)'.


0 REM Index Rooutine
10 DIM target 25,P% 100
20 osrdch=&FFE0 : $target="ABCDEFGH" : found= &70
30[
40.enter
50 JSR osrdch Get character
60 LDX#(LEN($target)-1) Length of string
70. Loop
80 CMP target,X Compare character
90 BEQ match Got a match
100 DEX Try again
110 CPX#255 UntiL end of string
120 BNE Loop
130.match
140 INX The position in the
150 STX found String is stored
160 RTS Return
170~
180 END

The routine is entered at '.enter', and as it stands it looks for one of the letters A to H.

5.3 Summary of addressing modes
The following sections summarise all addressing modes that are available on the 6502, some of which have been met already.

Immediate addressing
Use immediate addressing when the data for an instruction is known at the time of writing the program. In this mode the second byte of the instruction contains the actual eight-bit data to be used by the instruction. The '#' symbol denotes an immediate operand.

ExampLes: LDA #vaLue
CPY #flag + 2

Absolute addressing
Use absolute addressing when the effective address, to be used by the instruction, is known at the time the program is being written. In this mode the two bytes following the op-code contain the 16-bit effective address to be used by the instruction, the low byte being given first, followed by the high byte.

Example: LDA address

Zero page addressing
Zero page addressing is a subset of absolute addressing. They are similar in that the instruction specifies the effective address to be used; the difference between them is that in absolute addressing the address used can be anywhere, whereas in zero page addressing the address is in zero page, i.e. from &0000 to &OOFF. Hence this address is only one byte rather than two. The assembler will automatically produce zero-page instructions.

ExampLes: JSR Loop
ASL &9A

Indexed addressing
Indexed addressing is used to access a table of memory locations by specifying them in terms of an offset from a base address. The base address is known at the time that the program is written; the offset, which is provided in one of the index registers, can be calculated by the program.

In all indexed addressing modes one of the eight-bit index registers, X and Y, is used in order to calculate the effective address to be used by the instruction. Five different indexed addressing modes are available, and are listed below.

Absolute indexed addressing
The simplest indexed addressing mode is absolute indexed addressing. In this mode the two bytes following the instruction specify a 16-bit address which is to be added to one of the index registers to form the effective address to be used by the instruction.

ExampLes: LDA tabLe,X
LDX palette,Y
INC score,X

Zero,X indexed addressing
Here, the second byte of the instruction specifies an eight-bit address, which is added to the X-register to give a zero-page address to be used by the instruction.

Note that in the case of the LDX instruction a ‘zero,Y' addressing mode is provided instead of the ‘zero,X' mode.

ExampLes: LSR &80,X
LDX addr,Y (where addr+Y is in zero page)

Indirect addressing
It is sometimes necessary to use an address which is actually computed when the program runs, rather than being an offset from a base address or a constant address. In this case indirect addressing is used.

Indirect addressing is distinct from direct addressing (i.e. absolute, indexed, etc) in that the address specified after the mnemonic is used to refer to a location where the final address will be found. Thus the machine does not go directly to the address, but instead it goes indirectly, via the address given.

The indirect mode of addressing is available for the JMP instruction. Thus control can be transferred to an address calculated at the time the program is run.

ExampLes: JMP (&2800)
JMP (addr)

For the dual-operand instructions ADC, AND, CMP, EOR, LDA, ORA, SBC and STA, two different modes of indirect addressing are provided: preindexed indirect, and post-indexed indirect. Pure indirect addressing can be obtained, using either mode, by first setting the respective index register to zero.

Pre-indexed indirect addressing

ExampLes: STA (zerotable,X)
EOR (&60,X)


This mode of addressing is used when a table of effective addresses is provided in zero page; the X index register is used as a pointer to select one of these addresses from the table.

In pre-indexed indirect addressing the second byte of the instruction is added to the X register to give an address in zero page. The two bytes at this zero-page address are then used as the effective address for the instruction.

Post-indexed indirect addressing
This indexed addressing mode is like the absolute,X or absolute,Y indexed addressing modes, except that in this case the base address of the table is provided in zero page, rather than in the bytes following the instruction.

In post-indexed indirect addressing the second byte of the instruction specifies a zero-page address. The two bytes at this address are added to the Y index register to give a 16-bit address which is then used as the effective address for the instruction.

Examples: ADC (&66),Y
CMP (pointer),Y

This last addressing mode is very useful. An example of its use is given in the program below, which will only work on a machine without a second processor attached. It clears the screen.

10 DIM MC% 100
20 addr=&70
30 FOR pass =0 TO 2 STEP 2
40 P%=MC%
5O[OPT pass
60.cls
70 LDA #&58 High byte of start address
80 STA addr+1
90 LDY #0 Low byte of address,
100 STY addr and value to write to screen
110 TYA Put zero (black) into A
120.clsloop
130 STA (addr),Y store zero
140 INY
150 BNE clsloop do 256 times
  1. INC addr+1 Increment hi byte of adress
170 LDX addr+1 Set status register to addr
180 BPL clsloop Compare with top of RAM
190 RTS
200]
210 NEXT pass
220 MODE 4
230COLOUR 129
240 CLS White out screen
250 A=GET Wait for a key
260 CALL cls Black out screen
270 COLOUR 128
280 END



6 THE STACK

The 6502 processor supports a hardware stack. This is an important part of the computer which can be used both by the CPU and the programmer. This chapter looks at how the stack can be used and how it performs its task.

  1. Using the stack

A hardware stack is simply a set of memory locations (&100 to &1FF) which are reserved by the processor. These locations can be used as temporary storage locations. Up until now, when we have wanted to store a value, we might have used.

STA tempaddr

And to recall the value which was in the accumulator at that time, the instruction

LDA tempaddr

Loading the accumulator with the value from 'tempaddr' does not alter the value stored in 'tempaddr'. Hence the number may be recalled several times. When storing a value on the stack, however, the situation is different. The value to be stored is 'pushed' onto the stack and when it is wanted again,

it is 'pulled' off into a register - a one-time only operation.

The stack is a LIFO structure, these initials stand for 'last-in, first-out', which means that the first item put on the stack will be at the bottom and so will be the least readily available, whereas the last item on the stack will be at the top and will be the first one to be pulled off it.

The four instructions which a programmer can use to access the stack are:

Mnemonic Description

PHA push the contents of A onto the stack
PLA pull a value off the stack and store it in A
PHP push the contents of the status register P
PLP pull a value off the stack and store it in P

'Last-in, first-out': the stack forbids the use of any item other than in that order.

A stack pointer is used to manage the stack. This is a register which contains the address of the top location being used. For example, on encountering a PLA instruction, the accumulator is loaded from the memory location pointed to by the stack pointer and then the stack pointer is automatically moved back one location. Whenever a PHA instruction is executed, the accumulator is stored in the memory location pointed to by the stack pointer and then the stack pointer is moved on to the next location.

Example
The following series of pushes and pulls leaves the stack in a state which is shown below:

LDA #78
PHA
LDA #79
PHA
PLA
LDA #80
PHA


Stack pointer points to next free space
80
Last value pushed onto the stack
78
Previous value pushed onto the stack



Note that the value 79 which was pushed onto the stack and then pulled off it again no longer occupies a location on the stack.

To see why the stack is used as a temporary storage place in preference to a memory location such as tempaddr', consider the two alternative sections of assembler below:

PHA STA tempaddr
LDA addr LDA addr
CLC CLC
ADC #12 ADC #12
STA addr STA addr
PLA LDA tempaddr

These both produce the same result when executed but the one on the left will produce less code. This is because the LDA and STA instructions consist of either two or three bytes, one for the op-code and one or two for the address. The PHA and PLA instructions, however, just consist of an op-code.

Note that if more than one value is stored on the stack at once, care must be taken when these values are retrieved. Because it is a LIFO structure, the values must be taken off the stack in the opposite order to how they were placed on it, e.g.

PHA save accumulator
TXA prepare to save X
PHA save X
PLA restore value
TAX transfer to X the last vaLue pushed
PLA restore accumulator

When using the stack it is very important to pull as many values as you push. Otherwise confusion can arise as we will see below.

6.2 How the CPU stores addresses
The stack is used by the CPU as well as by the programmer. On encountering a JSR instruction, the address of the instruction following the JSR is stored so that the CPU knows where to start executing from when it comes to the end of the subroutine. This is done by pushing the two bytes of the address onto the stack so that they can be retrieved when the RTS is reached. Thus a routine which is entered with a JSR and finishes with RTS should always pull the same number of bytes as are pushed otherwise the value obtained from the top of the stack by the RTS will not be the correct return address, e.g.

.entersubroutine
PHA
LDA addr
CLC
ADC #12
RTS

When the RTS is reached the CPU will pull two values off the stack, and put them into the program counter. However, as one of these values is the value pushed with the 'PHA', the program counter will almost certainlv contain the wrong address. This will mean that the CPU will start trying to execute instructions at the wrong address and do something undefined by the designers of the 6502.

6.3 Recursion
One of the most important reasons for using a stack to hold the addresses of subroutine returns is that recursion is then automatically supported.

A recursive subroutine is one which calls itself. This can be a very powerful feature and enables a programmer to implement tree structures as shown below.

Using trees
A tree in computing is normally pictured as follows:
* root
/ \
/ \
* * node
/ \ / \
/ \ / \
* * * * branch
/ / \

Note that it is usuallv drawn with the 'root' at the top.

In order to print out all the elements (root and nodes) in the tree, you must write a routine which prints out an element and then goes down a branch to the element beneath it. If there isn't an element below, then it goes back up one level and sees if there is an alternative branch from there. For the above tree the order that the elements would be visited, assuming that the routine shows a preference for right-hand branches, is:

* 1 root
/ \
/ \
*6 * 2 node
/ \ / \
/ \ / \
8 * 7* *4 *3 branch


A BASIC routine to do this is as follows:

DEFPROCtree (element)
PRINT value(element)
IF right(element) THEN PROCtree(right (element))
IF left(element) THEN PROCtree(left (element))
ENDPROC

This assumes that each element has three things known about it: its value, the element that its right branch leads to (FALSE if no branch) and the element that its left branch leads to (FALSE if no branch). These should be stored in three arrays whose names are 'value', 'right' and 'left'.

The routine can be called using

PROCtree(0)

The assembler version of this is:

.enter
LDX #0 Start at root (zeroth element)
.tree
LDA value,X Get value of element
JSR printnumber See section 13.1 for details
LDA right,X Is there a right branch?
BEQ tryleft If not, try a left one
TAX
JSR tree Else take that branch
.try left
LDA Left,X Is there a left branch?
BEQ backup If not, go back up one level
TAX
JSR tree backup Else take that branch
RTS Return

In this case the block of memory locations starting with the address 'value' should contain the values of each element in turn: those starting at 'right' should contain the element to which each one's right-hand branch leads, and 'left' the element to which each one's left-hand branch leads.



7 MACROS

If an assembler programmer wants to use a block of instructions several times during a single program then this block only needs to be entered once. There are then two methods of using this block when it is needed. The first is to put a label before it and an RTS instruction at the end, and reference it as a subroutine using the JSR instruction which was described earlier. In this case the CPU will 'jump' to the label when it reaches the JSR and 'jump' back again when it reaches the RTS at the end of the subroutine.

The alternative method is to turn the block of instructions into a macro, the method for doing this will be explained later. Essentially what this does is to give this block of instructions a name and, when the assembler comes across this name, it inserts the instructions of the macro into the object code which it is producing so that the CPU does not have to perform any jumps when it is executing the code.

Hence the main difference between macros and subroutines is that subroutines are called at run-time and macros are called at assembly time.

The advantage of macros is that they are faster than subroutines since no jumps are needed during execution. The disadvantage is that if the macro is to be used several times, the resulting machine code program will have to contain multiple copies of the instructions represented by the macro, and thus be long. Using a subroutine would only require one copy of the instructions. Macros can be more useful than just an aid to save typing however, and this chapter explains some of their other features. Further examples can be found in section 12.5 (General purpose macros).

7.1 Generating and calling a macro
Consider the sequence of instructions:

ROR A : ROR A : ROR A : ROR A

This simply shifts the upper nibble of the accumulator into the lower nibble. A macro with the name 'FNrotateacc' containing this sequence can be set up outside the assembler program as follows:


DEF Fnrotateacc
[OPT pass
ROR A : ROR A ROR A : ROR A
] :=pass

This macro can then be called from inside the assembler with the statement
OPT FNrotateacc

The OPT statement is being used here as a dummy statement, simply to call FNrotateacc and have no other effect. We therefore arrange for the value of the function to be 'pass', which should be the value used in the initial OPT statement. Therefore on reaching this statement the assembler will generate the machine code corresponding to the assembler instructions of the macro, and place this in the object code. Then it will move on to the next instruction of the assembler program.

The flow of control when the program is being typed will look something like this:

LDA addr
FNrotateacc
STA addr

The flow of control when the program is being assembled will look something like this:

LDA addr
DEFFNrotateacc
ROR A
ROR A
ROR A
ROR A
:=pass
STA addr

The machine code produced will be as follows:
A5 81 LDA addr addr &81
6A ROR A
6A ROR A
6A ROR A
6A ROR A
85 81 STA addr

7.2 Macro parameters
Macros can take parameters, thus the previous example could be rewritten in such a way that it could rotate the accumulator any number of times (as long as this number is greater than 0):

DEF FNrotateacc(rotate)
FOR number 1 TO rotate
[OPT pass
ROR A
]
NEXT number
= pass

So, to rotate the bits in any memory location any number of times to the right, simply set up a macro as follows:

DEF FNrotate(address, rotate)
FOR number = 1 TO rotate
[OPT pass
ROR address
]
NEXT number
= pass

A typical call might be

OPT FNrotate(&3000, 4)

This would generate machine code to rotate right four times the bits in location &3000.

  1. Conditional assembly in macros
Macros can also be constructed to contain conditional instructions, so that they will assemble different pieces of code according to the parameters passed. For example, the following macro works out the shortest way of rotating the accumulator left:

DEF FNoptimumrotate(rotate)
IF rotate < 1 THEN = pass
IF rotate < 5 THEN FOR number = 1 TO rotate
[OPT pass : ROL A:]:Next number
ELSE FOR number = 1 TO (9-rotate):
[OPT pass : ROR A:]:Next number
= pass

7.4 Labels in macros
Labels cannot be used in the normal way inside macros. Consider, for example, the macro given below:

DEFFNstar
[OPT pass
LDX addr
BEQ exclamation
LDA #ASC"*"
JSR oswrch
.exclamation
LDA #ASC”!”
JSR oswrch
] : =pass

When the assembler reaches the 'BEQ exclamation' loop instruction on the second pass, it will give the offset the same address of the label which was set up the first time around. If forward referencing is not used then this problem will not occur, but it is still undesirable to use label names time and again. The program becomes very difficult to follow and to debug.

To use labels in macros that are called from more than one place, it is necessary to set up tables of labels. Thus if the label 'start' is used in a macro, an array called 'start' would have to be DIMensioned at the beginning of the program together with as many elements as the number of times the macro is called. If the macro with 'start' in it was called three times from within the program, the statement 'DIM start(2)' would have to be inserted before the first call to that macro took place. Also, each call to the macro would have to pass a parameter which contained a number (0,1 or 2) so that the correct label would be used. To illustrate:

DEF FNmacro(fred,jim,no)
[OPT pass
LDA fred
LDY jim
.start (no)
JSR oswrch
DEY
BNE start(no)
]
=pass

The above macro will print the contents of 'fred' as an ASCII character, 'jim' times. By convention the label number is always passed as the last parameter, and it is also a good idea to have all the macros using the same variable to hold the number (in this case 'no').

A typical call to the above macro might be

OPT FNmacro(addr, 45, 1)

This would use the label 'start(1)'.

8 BASIC I, BASIC II AND ELECTRON BASIC

There are, at the time of writing, two versions of BBC BASIC available for the BBC Microcomputer, known officially as BASIC and BASIC II. In this book, however, they are referred to as BASIC I and BASIC II respectively, and the name BASIC is used as a general term to cover either. This chapter looks at the differences between the two which affect the assembler programmer and provides BASIC I versions of the new directives and keyword.

The BASIC provided on the Electron can be assumed to be BASIC II. The BASIC routines given elsewhere in this book will all work on machines containing BASIC II and can be adapted to work with BASIC I as well.

8.1 Distinguishing BASIC I from BASIC II
To find out if you have BASIC I or BASIC II press
BREAK and then type REPORT. BASIC II will give
the message:

(C)1982 Acorn

Whereas BASIC I will produce:

(C)1981 Acorn

8.2 The main differences between BASIC I and BASIC II
The main differences between the two BASICs which concern assembler programmers are:

OPT
In BASIC I only the lowest two bits in the OPT statement are significant; if the lowest bit is set then the machine code is listed and if the next bit is set error messages are reported. The other bits are ignored. However, in BASIC II the third bit is significant as well; it is used to produce code which will execute somewhere other than the assembled position. If the third bit is set (OPTs 4 to 7) then the code is assembled at the value of O% (the code origin), not at P%. However, all the JMP's etc. will be set up as if it is going to execute at P%, and so it is an easy matter to relocate the code. This is particularly useful if, for example, you had written a routine which had to work in ROM, or some other space which is not normally accessible. Note that if this option is used then as the code is produced both O% and P% are incremented, otherwise just P% is incremented.

EQUB, EQUW, EQUD and EQUS
Four assembler directives have been introduced in BASIC II which are not present in BASIC I. These new directives are EQUB, EQUW, EQUD and EQUS which stand for 'equate byte', 'equate word' (2 bytes), 'equate double word' (4 bytes) and 'equate string' (0 - 255 bytes). These each take a single argument, and put its value into the assembly code at P% (also incrementing P% by the correct amount), e.g.

EQUB &FE Put ‘&FE' at P%

EQUW oswrch Put contents of 'oswrch' at P% and P%+1

EQUD 0 Set the next four bytes to zero

EQUS "Fred"+CHR$(13) Put 'Fred'+Carriage Return in memory starting at P%

OSCLI
A new kevword, OSCLI, has been introduced which takes as its argument an expression, which it then passes to the operating system command line interpreter. This is not directly useful in assembler source code, but is useful when saving or loading variable amounts of data, or when sending variable FX commands. For example the following routine sets up soft key 0 to contain the string 'LIST+ERL+ [RETURN]':

PROCkey (0, "LIST "+STR$ERL+"|M")
*
*
DEFPROCkey(number, A$)
OSCLI(“KEY " + STR$number + " " + A$)
ENDPROC

Another example is to SAVE a BASIC program by typing

OSCLI("SAVE <filename> " STR$~PAGE + “ “ + STR$~TOP)

Note: this is exactlv the same as the BASIC command SAVE <filename>. Note also the use of 'STR$~' here to convert a hexadecimal number to a string.

  1. BASIC I versions of EQUB, EQUW, EQUD and EQUS

Macros can be set up in BASIC I to emulate these directives:

EQUB
DEF FNequb(byte)
?P% = byte
P% = P% + 1
= pass
EQUW
DEF FNequw(word)
?P% = word AND &FF
P%?1 = word DIV &100
P% = P% + 2
= pass
EQUD
DEF FNequd(doubleword)
!P% = doubleword
P% = P% + 4
= pass
EQUS
DEF FNequs(string$)
$P% = string$
P% = P% + LEN(string$)
= pass

Note that 'FNequs' will put the string into memory from 'P%' on, and will also set the byte following the string to a &D byte (RETURN character), although the next mnemonic assembled will overwrite this. In the event that this is a problem, 'FNequs' could be rewritten so that only the string is put into memory, and nothing more. This is left as an exercise for the reader.

  1. BASIC I version of OSCLI

The following routine can be used in exactly the same way as OSCLI is used in BASIC II, e.g.

PR0Coscli("SAVE <filename> " + STR$~PAGE + “ ” + STR$~TOP

Note that 'cli' is an ATOM string which should be DIMensioned at the start of the program, e.g. DIM cli 64

DEFPROCoscLi ($cli)
LOCAL X%, Y%
X% = cli AND &FF
Y% = (cli AND &FFOO) DIV &100
CALL oscli oscli is at &FFF7
ENDPROC


9 OPERATING SYSTEM ROUTINES AND SPECIAL EFFECTS

A whole book would be needed to describe all the features of the operating system and the routines it contains. This chapter briefly introduces two operating system calls, 'OSBYTE' and 'OSWORD'. Between them, they perform a wide varietv of tasks. It then shows how operating system routines can be intercepted and replaced by user defined ones. Finally it takes a look at some of the special effects which can be obtained using either the operating system commands or specialised hardware provided by the BBC Microcomputer and Acorn Electron.

9.1 OSBYTE and OSWORD OSBYTE - &FFF4
OSBYTE calls can be used to access several operating system routines. The particular routine is selected by the number passed in the accumulator. The X and Y registers are used to pass any parameters needed to the routine and to pass back any results which may be produced as a result of the call, e.g.

LDA #12
LDX #1O
JSR osbyte

will call OSBYTE 12 which sets the keyboard auto repeat rate, in this case to 10 centiseconds.

LDA #129
LDX #&9D
LDY #&FF
JSR osbyte

will call OSBYTE 129 which performs the INKEY function, in this case it is being called with a negative value (&9D = -99) and performs a keyboard scan to see if the kev with this value, the Space bar, is being pressed.

On exit, X and Y contain &FF if the key being scanned was pressed and 0 otherwise.

*FX calls are used to access OSBYTE calls from BASIC. In this case the values of the registers are passed in the following way:

*FX 12,10

This will have the same effect as the first example.

However *FX calls do not return results so it is not appropriate to replace the second example given by a *FX call.

OSWORD - &FFF1
OSWORD routines are similar to OSBYTE routines, the difference being that parameters are not passed in the X and Y registers, instead they are passed in a parameter block, and the X and Y registers are used to contain the address of this block, e.g.

LDA #2
LDX #&80
LDY #&00
JSR osword

This calls OSWORD 2 which sets the value of the system clock to the five byte value which is stored in memory starting at the address &0080.

LDA #1
LDX #&80
LDY #&00
JSR osword

This calls OSWORD 1 which reads the system clock, the five byte value being returned in memory starting at the address &0080.

9.2 Revectoring operating system routines
Many of the operating system routines are not entered directly by jumping to their position in the ROM, instead they are entered via addresses stored in the RAM.

For example, to use the operating system write character routine (OSWRCH) (the instruction which has been used in previous examples is JSR &FFEE). Location &FFEE, however, contains not the start of the routine, but the instruction JMP (&20E), since the address of the code for OSWRCH is stored in locations &20E and &20F in RAM. These locations are known as the 'vector' for this routine.

Accessing routines indirectly, via vectors in the RAM has several advantages. In different operating systems the entry position of the routine may alter, but this will not affect the user since the instructions JSR &FFEE or JMF (&20E) will still access it. The difference will be dealt with by the operating system which will store the correct addresses in locations &20E and &20F.

In addition the user can intercept any of the routines by 'revectoring' them. For example he could change the contents of &20E and &20F so that they contained the address of a user defined routine. One use of this is shown below.

Pretty Printer - prettyprint
When printing text to the screen it is often difficult to ensure that words will not be broken at the end of the line. The following routine achieves this. When linked in, it will buffer characters up to a space or carriage retum character, and then only output the characters on the same line if there is room without splitting them. Note that the routine does not deal with control characters (codes less than 32) that have trailing characters. The routine should be linked into the OSWRCH vector at &20E - &20F, by typing
!&20E =!&2OE AND &FFFFOOOO OR prettyprint

Notice that the variable 'linelength' is set to the length of the line (19, 39 or79,depending on the screen mode selected).
On entry A holds the character to be printed. X and Y are irrelevant.
A typical call would be any call to OSWRCH.
On exit all registers have been preserved.
An example of the output of this is: (40 column screen)
This text is Prettily Printed This text is Prettily Printed This text
Is Prettily Printed This text is Prettily Printed This text is
Prettily Printed this text is prettili Printed

.prettyprint
PHA
STX savedx Save X register
LDX pointer Get line pointer
CMP #ASC” ” Is it a space ?
BEQ isspace
STA buffer,X Store character
INX Increment pointer
CPX # linelength Is buffer full ?
BNE exit If not, get next character
STX pointer Update pointer
BEQ newline Branch always
.isspace
CPX #0
BEQ exit
JSR getpos
LDX #0 set X to zero for printbuffer
LDA pos get cursor position( x-coord)
CLC
ADC pointer get cursor x+pointer
CMP # linelength If >= linelength
BCS newline print buffer
LDA pos If cursor is at .beginning
BEQ printbuffer of a line print the .buffer
LDA #ASC “ “ else print a space
JSR printchar
JMP printbuffer and print the buffer
.newIine
LDA #13
JSR printchar
LDA #10
JSR printchar
.printbuffer
LDA buffer,X get characters
JSR printchar print characters
INX increment pointer
CPX pointer if line pointer<> line end
BNE printbuffer then get next character
LDX #0
.exit
STX pointer save pointer
LDX savedx restore X register
PLA restore A
RTS Return
.getpos
TYA save Y
PHA push to stack
LDA #&86 osbyte 86 is read cursor position
JSR osbyte return pos and vpos in X and Y
STX pos X holds x pos of cursor
PLA restore Y
TAY
RTS
.printchar
JMP (oldoswrch) old oswrch holds original contents of &20E and &20F

9.3 Screen Scrolling

On both the BBC Microcomputer and the Acorn Electron, there are two screen-scrolling methods known as software scrolling and hardware scrolling. Software scrolling is often slow. If you define a text window to cover the whole screen (VDU 28,0,24,39,0 in MODES 6 or 7 only) and then scroll and screen (by moving the cursor off the bottom of the screen), you will notice the scrolling slowing down as it attempts to move all the screen memory up a line. An alternative and faster method of scrolling has been incorporated in the hardware.

A section of each computer incorporates a register designed to hold the start of screen memory.. In the BBC machine it is the 6845 CRTC (Cathode Ray Tube Controller), and in the Electron it is a section of the ULA (Uncommitted Logic Array). To employ this screen-scrolling method, it is only necessarv to change the number in the register. On the next vertical sync, the new screen will be displayed starting at that number. To scroll the screen up on the BBC machine, simply type:

MODE 6
VDU 23; 12, &oc; 0; 0; 0; VDU 23; 13, &28; 0; 0; 0;

and on the Electron

MODE 6
?&FEO2 = &A0 : ?&FEO3 = &30

To explain: on the BBC machine there are in fact two registers which control hardware scroll. These are registers 12 and 13. Register 12 contains the high byte of the start address, and register 13 contains the low byte. Things are not quite this simple however, as the start address held in the two registers is only to the nearest 8 bytes (1 character cell in the MODEs 0 to 6), and so the number put into the registers is the start address, DIV 8. In the above example, the new address is &6000 + 40 * 8 (=&6140) DIV 8, which is &C28, and so we put &C in register 12 and &28 in register 13.

On the Electron things are not quite the same. The address held in the hardware is not to the nearest 8 bytes, but to the nearest 64 bytes.

The value to put into the Electron's ULA is the address of the top of the screen, divided by 2. The two registers are at &FE02 and &FE03 (low byte and high byte). The address, &6140, is written there by working out &6140 DIV 2 (=&30A0), and then writing the low and high bytes of the new value into the registers.

You will have noticed that the hardware scroll operation is not exactly the same as that of the operating system scroll, in that the top line is filled not with spaces but with what was on the bottom line when the process began. This is because the memory map will 'wrap round', as in the following diagram;
0 1 2 3 37 38 39
0

&6140
&6148
&6150
.....
.....
&6268
&6270
&6278
1

&6141
&6149
&6151
.....
.....
&6269
&6271
&6279
2

&6142
&I614A
&6152
.....
....
&626A
&6273
&627B
3

&6143
&614B
&6153
.....
.....
&626B
&6273
&627C
4

&6144
&614C
&6154
.....
.....
&626C
&6274
&627C
5

&6145
&614D
&6155
.....
....
&626D
&6275
&627D
6

&6146
&614E
&6156
.....
.....
&626E
&6277
&6?7E
7

&6147
&614F
&6157
.....
....
&626F
&6277
&627F
8

&6280
&6288
&6290
.....
.....
&64E8
&64F0
&64F8


.....
.....
.....
.....
.....
.....
.....
.....
24*8+6

&7F46
&7F4E
&7F56
....
&606E
&6076
&607E
24*8+7

&7F47
&7F4F
&7F57
....
&606F
&6077
&607F

9.4 Palette handling
Both the BBC Microcomputer and the Acorn Electron provide a palette facility in the 'soft' screen modes. On the Electron all modes are 'soft' screen modes, but on the BBC machine Teletext (MODE 7) has no palette facility. The idea is that each mode can display a certain number of colours at any one time (16 in MODE 2, 4 in MODE 5 and so on).

Essentially the palette provides a mapping between the screen memory and what appears on the screen. The screen memory contains logical colours, and these are represented by the palette as physical colours which you see displayed on the screen. Thus in MODE 1, where there are four logical colours, each can be represented as any of the sixteen physical colours which these computers are capable of producing. Use the VDU19 statement to tell the computer how to represent a particular logical colour as a particular physical colour, i.e.

VDU 19, logical colour, physical colour ; 0 ;
For example

VDU 19,1,3;0;

This tells the computer to display all occurences of logical colour 1 in its memory as physical colour 3 (Yellow). Think of the palette as a mapping of physical colours onto logical colours, where all that the VDU 19 statement does is to simply change the mapping. The illustration below should make this clear;
Logical
Colour Phisical
Mode number colour

0,3,4.6 0 black
1 white

1,5 0 black
1 red
2 Green
3 Yellow
4 Blue
5 Magenta
6 Cyan
7 White
8 F(lashing) black / white
9 F red / cyan
10 F green / magenta
11 F yellow / blue
12 F blue / yellow
13 F magenta / green
14 F cyan / red
15 F white / black

Another way of changing the palette is to call OSWORD with A set to 12. In this you simply set up a block of 5 bytes to this format:

Paletteblock logical colour
paletteblock+ I Physical colour
paletteblock+2 0
paletteblock+3 0
paletteblock+4 0

Then OSWORD is called in the normal manner (with X and Y pointing to the parameter block, 'paletteblock' in this case). This has precisely the same effect as VDU 19, except that it is faster and also may be called from an interrupt or Event routine (See Interrupts below).

OSWORD 12 is not available on BBC machines with OS 0.10.

9.5 Interrupts, events and BREAKs Interrupts

Both the BBC Microcomputer, and the Acorn Electron run under interrupt'. Interrupts allow the machine to update its own internal variables, without the user even realising that their program is not in complete control.

On the 6502 there is an interrupt request pin (IRQ) which, when a signal hits it, tells the processor that an interrupt request has occurred. The 6502 then has the option of ignoring the interrupt. This decision is made by the state of an interrupt flag. If the flag is set, then the interrupt will be ignored, otherwise the operating system will deal with it.

The interrupt flag can be altered with the two assembler instructions:

Mnemonic Description
SEI set interrupt disable flag
CLI clear interrupt disable flag (Default state)

Note that the interrupt flag should not really be altered, as then all interrupt driven devices (keyboard, flashing colours, sound, etc.) would stop working.

There are two interrupt vectors provided by the operating system. These are IRQ1V (at &204), through which all interrupt requests are passed, and IRQ2V (at &206), through which any unrecognised interrupts are passed. Normally, IRQ2V would be used, but if you want to update your own device before the operating system can act, then you should use IRQ1V. The routine must first handle the interrupt, then disable the device that caused the interrupt, and finally, it must perform a 'JMF (oldIRQIV)' (where 'oIdIRQ1V' is the old contents of IRQI V). This should only be used if there is no other way of achieving the desired effect.

One way to set up interrupts on the BBC Microcomputer is by the User 6522 VIA (Versatile Interface Adapter). This has two timers, which can be set to count down from any particular 16-bit value, and to cause an interrupt request on reaching zero. Also, it will be necessary to write a routine to handle this, and to put the address of the entry point of the routine in the correct vector (in this case IRQ2V). Note that the routine must perform an 'RTI' in order to transfer control back to the operating system. RTI stands for return from interrupt.

All this might seem a bit messy, and so a second kind of interrupt peculiar to the BBC microcomputer and Electron has been implemented. This second kind of interrupt is called an Event. (It is not implemented on BBC Microcomputers with OS 0.1).

Events
These operate in a similar way to interrupts in that they are totally transparent (undetectable by the user program) and are indirect, via a vector (at &220).

Certain occurrences within the machine have events associated with them, and these events can be trapped by the user. These are:

0 - Buffer empty, where X gives buffer identitv
1 -Buffer full, where X gives buffer identity and Y holds character that could not be stored.
2 - Keyboard interrupt
3 - ADC conversion complete
4 - Start of TV field pulse (vertical sync)
5 - Interval timer crossing zero
6 - Escape condition detected
7 - R5423 receive error
8 - Remote procedure call detected (on Econet)

Events can be selectively disabled and enabled with OSBYTEs 13 and 14, where X specifies the event. Note that the default state is all events disabled.

Example event handler:

10 REM Event handler
20
30 vsynccounter=&70
40 DIM code 100
50 FOR pass = 0 TO 2 STEP 2
60 P%=code
70[ OPT pass
80.event
90 PHP preserve status
100 CMP #4 is this the event we want
110 BNE notvsync if not return
120 INC vsynccounter increment event counter
130.notvsync
140 PLP restore status
15'] RTS return
16O]
170 NEXT pass
180
190 eventvec = &220
200 ?eventvec = Fnlo(event)
210 eventvec?1= Fnhi(event)
220 *FX 14 4
230 END
240
250 DEF Fnlo(value) = value AND &FF
260
270 DEF Fnhi(value) = (value AND&FF00)DIV 256

BRKs
The 6502 supports a BRK instruction. This generates a software interrupt, which is similar to the interrupt request described earlier, except that it cannot be disabled. BASIC and the operating system use BRKs for flagging errors. This means that the BRK handler will print an error message. The standard format of the BRK error message is;

BRK instruction (op-code is &00)
Fault number (one byte)
Fault message (string of characters terminated by a zero byte)

Thus it is possible to put error messages in a program, and have them printed out by the BRK handler (which, incidentally, is normally handled by the language). This can be useful for debugging purposes. A useful macro for this is:

DEF FNerror(err, error$)
[ OPT pass
BRK Cause BREAK
EQUB err Fault number
EQUS error$ Error message
EQUB 0 Message terminator
.pass

A typical call to this would be:

OPT FNerror(60, "Hello")

where '60' is the fault number, and 'Hello' is the message to be printed when that BRK is activated.

It is, of course, possible to write your own BRK handler, by simply putting the start address of a suitable routine in the BRK vector (&202). For example, the following routine prints out all registers at a BRK:

0REM BREAK Handler
10
20 oswrch = &FFEE
30 osnewl = &FFE7
40 stringptr=&70
50 exit=stringptr
60 temp=strinqptr+2
70 exit = !&202
80 DIM code 200
90 FOR pass = 0 TO 2 STEP 2
100 P% = code
110[ OPT pass
120.header
130 EQUS " A X Y PC N V U B D I Z C”+ CHR$10 + CHR$ 13
140.break
150 TYA X and Y
160 PHA push all registers so they may be printed
170 TXA
180 PHA
190 LDA &FC Get accumulator
200 PHA
210 JSR osnewl go onto a new line
220 LDX #FNlo(header Print “A X Y PC..
230 LDY #FNhi(header)
240 JSR atomstring
250 PLA Get accumulator
260 JSR hexandspace print it and a space
270 PLA Get X register
280 JSR hexandspace Print it and a space
290 PLA Get Y register
300 JSR hexandspace print it and a space
310 LDA &FE FE an FD hold the
320 JSR printhex program counter where
330 LDA &FD the break occured
340 JSR hexandspace
350 PLA Status register
360 JSR printbinary Print P in binary
370 JMPexit return to old error
380
390. hexandspace
400 JSR printhex Print A in hexadecimal
410 LDA #ASC" “ load a space
420 JMP oswrch print it
430
450 PHA Save accumulator
460 LSR A get
470 LSR A top
480 LSR A nibble
490 LSR A
500 JSR print print top hex digit
510 PLA get bottom nibble
520 AND #&OF
530.print print bottom hex digit
540 CMP #&OA
550 BCC notalpha if not 0 – 9 get char
560 ADC #6 carry is set here (add 7)
570.notalpha
580 ADC #&30 convert to ASCII
590 JMP oswrch print and return
600
610.printbinary
620 LDX #8 eight bits per byte
630.binaryloop
640 ASL A get a bit
650 STA temp print either 0 or 1
660 LDA #ASC"0"
670 BCC printzero
680 LDA #ASC"1"
690.printzero
700 JSR oswrch print 0 or 1
710 LDA #ASC" “ followed by a space
720 JSR oswrch
730 LDA temp
740 DEX
750 BNE binaryloop Get next bit
760 RTS return
770
780.atomst ring
790 STX stringptr Address of string
800 STY stringptr+1 is given in X and Y
810 LDY #&I00 Y is pointer along string
820.atomstringloop
830 LDA (strinqptr),Y get next character
840 JSR oswrch print it
850 INY increment pointer
860 CMP #13 is it a return
870 BNE atomstringloop if not repeat loop
880 RTS return
890
900]
910 NEXT pass
920 !&202=!&202 AND &FFFF000O OR break
930[OPT 2
940.test
950 LDA #&01
960 LDX #&23
970 LDY #&45
980 SED
990 CLC
1000 BRK
1010 EQUB 75
1020 EQUS “HELLO”
1030 EQUB 0
1040]
1050 CALL test
1060
1070 DEF FNIo(value)=value AND &FF
1080
1090 DEF FNhi(value)=(value AND &FF00)DIV &100


>RUN

A X Y PC N V U B D I Z C
01 23 45 1BB6 0 0 1 1 1 0 0 0
HELLO at line 1050


I0 LARGE ASSEMBLER PROGRAMS

When writing substantial assembler programs it soon becomes evident that even 32K of memory is insufficient to hold both the assembler source text and the object code produced. This difficulty is heightened still further if the graphics modes are used. The problem can be overcome by breaking the source text into smaller modules or files and this chapter looks at how to set these up and how to use them.

  1. Source files and the master compiler' program
A source module is a program which acts like a subroutine but has assembly code inside it. The master 'compiler' program reads in each source text module and assembles it. This program is shown below. (Note that anything enclosed within < > (angled brackets) is not to be typed in, but is to be replaced with the value for your application.)

0 REM Compile program
10 origin = <start of area for machine-code>
20 file$ = "ABC"
30 PROCrun("I",<page for source files>)
40 PROCrun("M",<page for macro file> (optional))
50 FOR pass = 0 TO 2 STEP 2
60 P% = origin
70 FOR files = 1 TO LENfile$
  1. PROCrun(MID$(file$,files,1),<page for source files>)
  2. NEXT files
  3. NEXT pass
110 PRINT '"Object code from &'';~or igin ''to &;~P%
120 END
130
140 DEFPROC run (name$,start)
150 PRINT name$;
160 OSCLI "LOAD SOURCE"+name$+" "+STR$~start
  1. PAGE = start
180 GOSUB 0
190 ENDPROC

The files are assumed to be called SOURCEA, SOURCEB, ... i.e. the word 'SOURCE' followed by a single letter. The string 'file$' holds the letters which identify them and hence in this example it contains 'ABC'. Since single letters are used, the length of 'file$' gives the number of source programs. Note that in this example 'I' and 'M' should not be used for naming source files since they have their own special uses.

The program starts by reading in and assembling SOURCEI which is the initialisation file to be described later in this chapter. Then the macro file (if one exists) will be treated similarly; again this will be explained later. The main loop takes each of the source files in turn, loads it into the area you have defined as reserved for the source files, and then assembles it. For the first source file the object code starts at the value of the variable 'origin', and P% is used by the assembler as a pointer to the next free byte. Hence this allows subsequent source files to be assembled so that their code follows on directly after that produced by the previous one.

A typical source file would be of the following format:

0 REM SOURCEX
10
20~OPT pass
30
...(Assembler text)
...
...
...
900
910] : RETURN

A typical memory map might look like this:

Start of BASIC Screen - Smallest MODE possible (MODE 7)

Variables shared by all source files

COMPILE program

Macro source file

Source files


Origin Object Code


Note that if you are using a 6502 second processor then the screen mode selected will not make any difference.

This method has been designed for use with disc based systems, but can also be used on cassettes if the tape is rewound between the two passes. To remind you of this you should place a 'Rewind Tape' message, together with a 'dummy=GET' statement (to wait for a key to be pressed as an indication that the tape is in the correct position), between the two NEXT statements.

10.2 Saving source files

One routine which is useful when using discs and the above method is a PROCsave routine;

DEF PROCsave OSCLI("SAVE <filename>+ STR$~PAGE +” “+STR$~TOP)
ENDPROC

Note: this routine will not work on BASIC I. See chapter 9 (BASIC 1, BASIC II and Electron BASIC) for a description of OSCLI and the equivalent BASIC I routine.

The routine should be inserted at the ends of all the source files, and called whenever you wish to save the source file that is in memory at the time.

Thus to SAVE the program all that is needed is to type 'PROCsave'. The reason this is so useful is that when editing large numbers of source files which all look alike, it is very easy to overwrite an existing file by typing the wrong name.

An alternative to this, which works on all versions of BASIC, is to type, in immediate mode, 'SAVE $(PAGE+6)'. This looks at the first line of the program to find the filename. So, each program should start with

0 REM <filename>
100 <program>
200 ....

Default soft keys
Another useful idea is to employ the machine's soft keys. This is best done by having a default soft keys program, which can be loaded at the beginning of the session.
10 REM Default Soft Keys
20
30 *KEY 0 I LLIST I N I M
40 *KEY 1 RUN | M
50 *KEY 2 LOAD"SOURCE
60 *KEY 3 CALLenter | M
70 *KEY 5 PPOCsave I M
80 *KEY 6 PROCfind("
90 *KEY 7 MODE7 I M PAGE=&600 I L0AD "COMPILE" I M
100 *KEY 9 I L*CAT I M

Note that line 80 has a reference to PROCfind. This procedure is to be used from immediate mode to find all occurrences of strings in the current source module. (PROCfind is defined in section 12.6.) This procedure should be at the end of every source file.

  1. Macro source files

If source files are to be used then calling macros can be a problem. To understand why, some knowledge of how BASIC works is required. When BASIC first comes across a reference to a function or procedure its search for the function or procedure definition starts from PAGE. Once it has found the definition it stores the address of the start of the function or procedure in memory, so that the next time it finds a reference to that particular function or procedure it doesn't have to waste time searching through the whole program again.

In the simplest case, when just one source file references a given macro, the macro can be added to the end of that source file, and the file treated normally. Consider, however, what would happen if two source files both had a reference to a macro called 'FNfred', and this macro was put at the end of each of them. Since they would almost certainly be different sizes the definition of 'FNfred' would, in each case, start at different addresses. Moreover, when the first source file was assembled the address of the macro in this file would be stored for use by all later references to the macro. Thus, when the second source file tried to use the macro, no searching through would occur. Instead the assembler would jump straight to the address which was stored by the first file and be unlikely to find 'DEFFNfred' starting there.

To avoid this problem a macro file is set up containing all the macros referenced in any of the assembler source files. This is present in the memory all the time, though in a different area of memory to the other files. Each of the macros must be called before the source files are assembled, however, so that the addresses where their definitions may be found are available to the compiler. Otherwise the first time that the compiler comes across a reference to a macro, e.g. FNfred, it will start searching for 'DEFFNfred', starting at PAGE, look through to the end of the source file, not find the definition, conclude that the macro doesn't exist and report 'No such FN/PROC'. So, set PAGE to the bottom of the macro file and call each macro once.

Thus a typical macro source file looks like this:

0 REM Macro file
10
20 pass = -1 : REM Dummy compilation
30 A% = Fnmacro1(0,0) + FNmacro2(0,0,0,0)..
40 RETURN
50
60 DEF FNmacro(temp, no)
70 IF pass <0 THEN = TRUE
8O[OPT pass
90 ...

Line 20 sets 'pass' to a value that the assembler will not use, so that the first time the macro is referenced it will not generate any code. Note that every macro in the file must have a test to see if it is being referenced for the first time (as at line 70).

Since the macro file needs to be resident in memory during the compilation a space will have to be assigned for it. This is usually between the top of the normal source files and the bottom of the COMPILE program.

10.4 Initialisation file

The initial file mentioned in COMPILE (line 30) is the file that sets up all variables to be used in the later source files. This is normally in the form:

0 REM SOURCEI
10
20 REM variables
30
-------------------------------------------------------------------
This is where all the variables that will be accessed by all the source files are defined. Note that all the variables are defined to be resident in memory one after the other. Thus to move the block of variables around in memory, all you have to do is to simply change the value of 'P%' in line 40.
---------------------------------------------------------------------
40 P% = <start of memory to put variables>
50[OPT2 \ Report any errors, but don't list
60.<first variable> EQUB 0 \\ Reserves one byte
70.<second variable> EQUW 0 \Reserves two bytes
30.<third variable> EQUS STRING$(20,CHR$(0))
90 etc.

200]
-----------------------------------------------------------------------
This section sets up any constants that may be used in the program. The use of constants in any program is very important as is explained in chapter 12 (Program structure).
------------------------------------------------------------------------
210
220 REM Constants
230
240 limit = 45
250 numberofshapes = 12
260 oswrch = &FFEE
270 etc.
-------------------------------------------------------------------------
This section reserves memory for the data tables.
-------------------------------------------------------------------------
300
310 REM Main Memory Allocations
320
330 P%=<start of memory allocated for tables>
340[OPT2
350.jim
360 OPT FNspace(80)
370.fred
380 OPT FNspace(300) See section 12.5
390.lenqth for FNspace
400 OPT FNspace(1OO)
410 etc
....
.....
----------------------------------------------------------------------------
This section fills the data tables.
----------------------------------------------------------------------------
600
610 REM fill data tables
620
630 FOR offset = 0 TO numberofshapes
640 READ offset?length, offset?width, offset?type
650 NEXT offset
660
670 DATA 20,12,3 ,36,24,1 ,etc.
680 etc
999 RETURN


II PROGRAM STRUCTURE

The aim of this chapter is to help you write a large assembler program. It gives several tips how to produce structured and readable code which can be debugged easily.

11.1 Where to start
There are two entirely different approaches which can be used when producing a structured program. The first is to work out what the program is going to do and what it is going to look like before you start writing any code. Then you can start by writing the main loop which, for a game, could look something like this:
. enter
JSR initialise
. restart
JSR setupscreen
.main
JSR plotshapes
JSR checkcollisions
JSR keyboardscan
JSR updatecoordinates
JSR checkifdead
BNE main loop
DEC Iives
BNE restart
RTS

Although, at this stage, the subroutines have not been defined, it is obvious from their names what they are meant to do. The next task is to write these subroutines, again breaking them down into simpler routines if this is possible.

Since the main loop is the top level in the overall structure, this method is known as 'top-down' approach. Its advantage is that you know what you are aiming at right from the start. You shouldn't need any 'fixes' in the subroutines to make up for the fact that when you wrote them you didn't make them apply to all cases eventually required.

The alternative method is to start by writing the individual routines and then to add the code which joins them together into the final program. The advantage of this method is that all routines can be tested individually before very much effort has gone into the program. Consider how much time would be wasted if you used the first method and completed the whole program except for one routine which proved impossible to write in such a way that it performed satisfactorily.

In practice most people use a mixture of the two methods, so that any demanding routines are written first; then the top-level is written, followed by the rest of the program.

11.2 Self-documenting code
Long, directly referential variable names are a good idea when writing a program as they make the program more intelligble, e.g. 'JSR updatecoordinates' is better than 'JSR label2'. These may make the source code longer, but the problem can be overcome by splitting up the code into separate sections and compiling these individually as described in chapter 10 (Large assembler programs). Note that the fewer JMP's there are, the easier it is to follow the flow of control. Hence, using macros rather than subroutines also helps to increase the clarity of the code.

11.3 Parameters
Parameter passing is also recommended in assembler, for precisely the same reasons as it is in BASIC, viz. It enables a single block of code to be used for more than one purpose. However, the method of achieving this is somewhat different. Parameters are usually passed in the three registers A, X and Y, or alternatively the X and Y registers are used to specify an address of an information block, and the A register then holds some other information. A third method is to pass parameters in specified locations, so enabling an arbitrary number of parameters to be passed. This method doesn't support nesting or recursion, however.

The CALL statement
This statement, which is used to transfer control from a BASIC program to a machine code program, can also pass parameters. When it is used, the bottom bytes of the BASIC variables A0/0, X% and Y% are transferred to the A, X and Y registers respectively. Also the lowest bit of C% is transferred to the carry flag.

Control is passed to the address given after the CALL statement and any parameters following this address are put into the parameter block starting at &600. The parameter block is of the following format:

&600 Number of parameters
&601 Parameter address (low byte)
&602 Parameter address (high byte)
&603 Parameter type
&604 Parameter address (low byte)

The parameter types are as follows:

Type 0 8-bit byte
Type 4 32-bit word
Type 5 40-bit floating point string
Type 128 ATOM string
Type 129 Microsoft string

In the case of a string parameter the address given points to a 'string information block', which contains the following:

Start address of the string
Number of bytes allocated
Current length of string

An example of a CALL statement is

CALL enter,fred,A$

This would cause the machine code from 'enter' to be executed and the parameters 'fred' and 'AS' would be described in the parameter block as follows:

I 02 I C7 I 0E I 05 I DO | 0E | 81 I ?? I ?? I ?? I
0600 0601 0602 0603 0604 0605 0606 0607 0608 0609

The USR function
Another way of transferring control to a machine code routine is via the USR function. The differences between CALL and USR are that USR returns a result, a four-byte number consisting of the Status, Y, X and A registers (most significant byte to least significant byte), and takes only one parameter which is the address to which control is transferred.

11.4 Size of routines
All routines, whether in BASIC or assembler, should be as small as possible, so that they can be seen easily in their entirety. This is another real help when debugging. Debugging is often overlooked when estimating the time needed to write a program, and yet it is probably true to say that at least 50% of the time taken to write a program is taken up with debugging.

11.5 Conditional assembly as an aid to debugging
Conditional assembly can be used to insert extra instructions which print out intermediate values during debugging: these statments can be removed when the program is finally assembled. To do this a logical variable ('flag' in the following example) is given the value FALSE during debugging and TRUE otherwise. In the following example, if 'flag' is FALSE a routine to print the value of the accumulator in hexadecimal notation is assembled, and calls to this routine is inserted at two relevant points in the test program.

10 REM print hex digits
20 DIM code 100
30 oswrch = &FFEE
4O FOR pass = 0 TO 3 STEP 3
50 P% = code
60[OPT pass
70.enter CLC :ADC #&40 :]
80 IF flag = FALSE [OPT pass : JSR debug:]
90[OPT pass
100 BEQ exit :SBC :#&10:]
110 IF flag = FALSE [OPT pass : JSR debug:]
120 [OPT
130.exit RTS:]
140 IF flag THEN 360
150[OPT pass
160 # print hex digits
170.print
180 AND #&0F get bottom four bits
190 CMP #&0A if less then 10 then miss
200 BCC P% + 4 the next instruction
210 ADC #&06 Add 7 (6 + carry)
220 ADC #ASC”0 “ ADD ADC (0)
230 JMP oswrch Write the character
240 # print A in hex
250.debug
260 PHA
270 PHA
280 LSR A exchange top four bits
290 LSR A for bottom four bits
300 LSR A
310 LSR A
320 JSR print print first hex char
330 PLA
340 JSR print print second hex char
350 PLA restore original value of A
360 RTS return
370]
38ONEXT pass

The program works by finding out whether or not the four bits corresponding to each hex digit represent a number less than ten. If it is less than ten then the value ASC ("0") is added and the character, which will be a number 0... 9 will be printed. If it is greater than or equal to ten then a number equivalent to ASC ("A-10") is added so that ten will be printed as A, eleven as B etc.

For debugging purposes this program is assembled by typing

15 flag = FALSE
RUN

The program can then be executed for various values of A% by typing

A% = &12 : CALL enter

The final version of the program is assembled, without the debugging aids, by typing

15 flag = TRUE
RUN

11.6 Lower case variable names
As a convention, lower case is used for variable names. Most people consider this to make the code more readable. It also means that there is no chance of using variable names that conflict with BASIC's keywords e.g. 'print' can be used as a variable name, even though PRINT cannot).

11.7 Constants
Constants are used to give names to numbers which
will be used several times throughout a program.
They help to make the code easier to understand, e.g.

LDX #initial-lives

explains far better what is happening than

LDX #3

Using constants has another advantage - if a value needs to be changed and constants have been used, only the definition of the constant would need to be altered, rather than every occurrence of that value.

Some languages support constants and variables as totally different data types, and make it impossible to change the value of the constant. BASIC does not treat variables and constants differently (except for it's own constants, e.g. PI) and so it is up to the programmer to make sure that any constants defined retain their value. One convention used for this is to prefix all constants with a pound (£) sign as a reminder.

11.8 Lookup tables
Lookup tables are useful when converting one value to another. As an example consider the following:

LDY index
LDA table, Y

In this example the value of A is dependent upon the value of Y. The example consists of a table of values, starting at 'table'.

Note that the above assembler is directly equivalent to the BASIC 'A=table?index'.

The values in 'table' could be the values of a palette, for example

.table
EQUB &3 # Colour 0 is yellow
EQUB &1 # Colour 1 is red (default)
EQUB &6 # Colour 2 is cyan
etc.

Thus lookup tables should be used wherever it is necessary to produce a value from another value, but only when there is no simple realationship between the two.

11.9 Use of absolute addresses
A mark of a good assembler program is that it will contain no absolute addresses in the assembler source code. Thus, if a data table starts at location &30F6, a constant should be set up initially to have the value &30F6, and the constant used in the assembler code, not the number &30F6. This follows on from the above points, and also makes the code easier to read and alter.




12 UTILITIES FOR ASSEMBLER PROGRAMS

This chapter consists of a number of routines which are designed to be used in any assembler program. Typical calls are given for each, as are the values to be passed to the routines in the registers, and those values to be returned by them.

Note that these routines are not meant to be complete programs and cannot be run without additional code being added, e.g. assignment of values to any addresses being used and all the necessary assembler directives. If you wish to use any of these routines inside your own programs then the comments to the right of the assembler statements may be omitted. If the routines contain any BASIC commands then any comments to the right of these must be left out or preceded by REM statements.

  1. Input/Output

Print BCD number - printnumber
The 6502 microprocessor can perform arithmetic in two ways. These are binary (normal addition), and binary coded decimal (BCD). In the second method, each byte is split up into two nibbles, each of which can hold a decimal digit (0 to 9). Thus the largest number that can be held in one byte using this method is 99. The advantage of this method is that it is easier to output a binary coded decimal number in decimal than it is to output a straight binary number in decimal.

The following routine takes a BCD number in the accumulator and prints it, with leading zero suppression, at the current cursor position.

On entry, A contains the number to be pnnted, Y contains the leading zero flag (0 for no suppression, else suppress zeros), and X contains the ASCII code of the character to be printed in place of leading zeros.

A typical call to print out a two-byte BCD number, with leading zeros being replaced by spaces, would be:

LDX #ASC" Set Leading zero character to space
LDY #&FF Set Leading zero fLag
LDA highbyte Get top byte of number
JSR printnumber Print it
LDA Lowbyte Get bottom byte of number
JSR printnumber Print it

On exit X has been preserved and A and Y will have been corrupted.

.print number
PHA Save number
OPT FNrotateacc (4) Get top digit (See section 8.2)
J SR printit Print it
PLA Restore number
AND #&0F Get bottom digit
.printit Fall through
BNE validchar if non-zero print
TYA Check zero flag
BNE Leadingzero if set must be leading zero
.vaIidchar digit ok
LDY #&00 clear zero flag
ORA #ASC”0” add in ASCII zero
JMP oswrch print and return
.Leadingzero
TXA Get leading zero character
JMP oswrch print and return

Keyboard scan - inkey
This routine can be called to detect if a key is being held down at a particular instant; it uses INKEY of negative numbers.

On entry, X specifies the key to be tested. For details of the values for each key, see the table of INKEY negative numbers in Appendix A.

A typical call would be:

LDX #firstkey
JSR inkey
BNE keypressed

On exit, the zero flag is set or cleared depending upon the key's position at the time of testing. This routine does NOT go via the keyboard buffer. A and Y will have been preserved.

.inkey
PHA Save A
TYA Save Y
PHA
LDY #&FF Negative numbers
LDA #&81 osbyte &81 is INKEY
JSR osbyte Do it
PLA Restore Y
TAY
PLA Restore A
CPX Adjust zero f lag
RTS and retiJrn

Sound
Sound routines are always useful in games programs. The routine below works by using a table to hold all the sounds that it is to play, and, on entry, the number of the sound to be played is given. To set up the table, the following could be used:

FOR offset = 0 TO <number of sounds> * 8 STEP 2
READ sndbuff!offset
NEXT offset
DATA 1,-15,200,2O
DATA 3,1,150,10
DATA etc.

Where 'sndbuff' is the table to be filled with sounds, on entry, A holds the number of the sound to be played. X and Y are irrelevant.

A typical call would be:

LDA #firingsound
JSR sound

On exit, all registers will have been corrupted.

.sound
ASL A multiply soundbuffer by 8
ASL A
ASL A
ADC #FNlo(sndbuff) add in address of sound table
TAX put low byte of addres in X
LDY #Fnhi(sndbuff) Get hi byte in Y
BCC nohibyte If carry then X and Y correct
INY else increment hi byte
.nohibyte
LDA #&07 OSWORD 7 is sound
JMP osword

Print strings
ATOM style string - atomstring
ATOM strings are defined as being groups of characters terminated by a RETURN character (&OD).
On entry, X and Y hold the start address in memory of the string to be printed (X holds low byte, and Y holds high byte). A is irrelevant.

A typical call would be:

LDX #FNIo (hiscorestring)
LDY #FNhi(hiscorestring)
JSR atomstring

On exit, all registers will have been corrupted.
.atomst ring
STX stringptr Address of string to be printed
STY stringptr + 1 is given in X and Y
LDY #&00 Y is pointer along the string
.atomstringloop
LDA (strinqptr),Y Get next character from string
JSR oswrch print it
INY increment pointer
CMP #&0D is character return
BNE atomstringloop no? go back to start of loop
RTS return

microsoft style strings - microsoftstring
Microsoft strings are defined as being groups of characters, preceded by a byte giving the length of the string. A microsoft string can be set up as follows:

.fredstring
EQUB LEN(fred$)
EQUS fred$

On entry, X and Y hold the start address of the string (X holds low byte, Y high byte). A is irrelevant.

A typical call might be:

LDX #FNlo(fredst ring)
LDY #FNhi(fredstring)
JSR microsoftstring

On exit, all registers will have been corrupted.

.microsoftstring
STX stringptr Address of string to be printed
STY stringptr + 1 is given in X and Y
LDY #&00 Set pointer to length byte
LDA (stringptr),Y Get length of string
STA len Save length
.stringloop
INY loop counter
LDA (stringptr),Y get next char from string
JSR oswrch print it
CPY len printed all chars?
BNE stringloop no ? go back to start of loop
RTS

Centre a string - centre

This routine will centre up microsoft strings on the current line of the cursor. The reason that only microsoft strings can be centred using this routine is that their length is far more accessible than the length of an atom string, although the routine could be adapted for atom strings.

The BASIC equivalent of the routine given below is:

PRINT SPC((screenwidth - LEN(A$)) DIV 2);A$

On entry, X and Y point to the string to be centred, and A is irrelevant.

A typical call would be:

LDX #FNho(string) Point to string
LDY UFNhi(string)
JSR centre Centre it

On exit, all registers will have been corrupted.

.centre
STX stringptr Save Low byte of start address
STY stringptr + 1 Save high byte of start address
LDY #&OO Prepare to get Length byte
LDA #screenwidth Get screen width
SEC
SBC (stringptr),Y Subtract length
LSR A Divide by 2
TAX And transfer it to X
LDA #ASC" “ Stand by to print X spaces
.centre loop
JSR oswrch Print a space
DEX Decrement counter
BNE centreloop and loop until counter is zero
LDX stringptr Restore string pointers
LDY stringptr+1
JMP microsoftstring And print the string

Move cursor to X, Y - printtab
This is a very simple routine to move the text cursor to X, Y. It simulates BASIC's 'PRINT TAB (X, Y)'.

On entry, X and Y hold the X and Y coordinates of the position that the text cursor is to be moved to
, A is irrelevant.

A typical call might be:

LDX #xcoord
LDY #ycoord
JSR printtab

On exit, X and Y will be preserved, and A will have been corrupted.

.printtab
LDA #31 VDU 31 (move textcursor to X,Y
JSR oswrch
TXA send X coordinate
JSR oswrch
TYA send coordinate
JMP oswrch do it and return

Double height characters - double

This next routine will only work in Teletext mode, and is thus only suitable for the BBC microcomputer.

The BASIC equivalent of this routine is:

DEF PROCdouble(A$)
vpos% = VPOS : pos% = P0S
FOR string% = 0 TO 1
PRINT TAB(pos%, vpos% + string%);CHR$&8D; A$;CHR$&8C;
NEXT string%
ENDPROC

On entry, X and Y point to the string that is to be printed in double height, A is irrelevant.

A typical call would be:

LDX #FNlo(string)
LDY #FNhi(string)
JSR double

On exit, all registers will have been corrupted.

double
STX stringptr Save start address of string
STY stringptr +1
LDA #&86 Read text cursor position
J SR osbyte
STX pos and store it
STY vpos
LDX #&02 Print string twice
STX count
.double loop
LDX pos Move cursor to X,Y
LDY vpos
JSR printtab
LDA #&8D Teletext code for Double height
JSR oswrch
LDX stringptr Restore string start address
LDY stringptr + 1 Either 'microsoft' or 'atom'
JSR string To centre string JSR centre
LDA #&8C Teletext Normal height code
JSR oswrch
NC vpos Move down a line
DEC count Done it twice yet
BNE doubleloop If not then do it again
RTS Else return

Palette handling - ospalette

This routine performs VDU 19, Y, A, 0, 0, 0.
On entry, Y contains the logical colour to be defined, A contains the physical colour to change Y
to. (See section 9.4 for details of palette handling.) X is irrelevant.

A typical call might be:

LDY #logicalcolour
LDA #physicalcolour
JSR ospalette

On exit, Y and X have been preserved, A has been set to zero.

.ospalette
PHA Save physical colour
LDA #19 VDU 19
JSR oswrch
TYA Get logical colour
J SR oswrch VDU it
PLA Get physical colour
J SR oswrch VDU it
LDA #&00 Pad out with zeroes
JSR oswrch
JSR oswrch
JMPoswrch and return

Another routine for those of you with Electrons or BBC micros with 1.0 or 1.2 Operating Systems
is to change the palette with OSWORD 12. This has the advantage of being able to be called
from an interrupt routine.

.ospalette
STY paletteblock Same format as VDU 19
STA paletteblock
LDX #FNIo(paletteblock)
LDY #FNhi(paletteblock) Note that all registers are corrupted
LDA #12
JMP osword

This is called in the same manner as before. Note that the 'paletteblock' must contain zeros in the last three locations before the routine is called.

Wait for flyback - vsync
The next routine is a must for animation. It will wait for the electron beam inside the VDU (Visual Display Unit) to reach the top of the screen in BBC Microcomputers or the bottom of the screen in Acorn Electrons, and will then return. This is when all shapes should be updated to avoid flickering.

On entry, all registers are irrelevant. A typical call would be:

JSR vsync

On exit, all registers will have been corrupted.

. vsync
LDA #19 Osbyte 19 is wait for vsync
JMP osbyte (vertical sync roni sat ion)

or

. vsync This routine is only for Issue 0.10 Operating Systems on
LDA #&02 the BBC micro
STA viaier This is at &FE4E
.vloop
BIT viaifr viaifr stands for Versatile Interface Adapter Interrupt flag
BEQ v loop Register, which is at &FE4D
LDA #&82
STA viaier
RTS


12.2 Analogue to digital routines
Analogue to digital value - adval
This routine reads any A to D channel.

On entry, X holds channel to be read. Y and A are irrelevant.

A typical call might be:
LDX #1 Get value of channel
JSR adval

On exit, the value is returned in Y and X (Low byte and high byte respectively).

.advaI
LDY #0 Get ADVAL (X)
LDA #~8O
JMP osbyte


Joystick handler -joystick
This routine reads either of the two joysticks connected to the A to D converter. Note that the sensitivity of the reading is dependant upon the value of the constant 'joyrange' (0 - insensitive to 127 - very sensitive). The variables 'xcoord' and 'ycoord' are user variables.

On entry, X holds the number of the joystick to be read (1 or 3). A and Y are irrelevant.

A typical call might be:

LDX #1 Get readings of first joystick
JSR joystick

On exit, all registers will have been corrupted.


.joystick
STX temp Preserve joystick number
JSR adval Get horizontal reading
LDX temp Restore joystick number
CPY #joyrange Is reading within Limit ?
BCS tryleft See if within other limit
.right
INC xcoord Go right
.tryleft
CPY #256-joyrange Is reading within limit ?
BCC getotherpot no? try vertical component
.left
DEC xcoord go left
.getotherpot get vertical component
INX Get adval (joystick + 1)
STX temp preserve as before
JSR adval get reading
LDX temp restore joystick number
CPY #joystick all this is as above
.down except that y coordinate
DEC ycoord is being adjusted
.tryup
CPY #256- joyrange
BCC tryfire
.up
INC ycoord
.tryfire Get fire button
TXA Halve X (for fire button
LSR A mask)
STA temp (ADVAL (0) AND X DIV 2)
LDX #0 Get ADVAL (0)
JSR adval
TXA X holds fire button status
AND temp AND with mask
BEQ exit Not held down, then exit
.fire
JSR firebullet Else do something exit
.exit
RTS Return

Oscilloscope
The program displays the four A to D channels as four different colours (colours 1 to 4). This program will only work on the BBC Microcomputer Model B, as it uses MODE 2, the Analogue to Digital converter, and Hardware scroll. The program also only reads the top eight bits of each channel, as the graphics vertical resolution is only 256. Sampling of the channels takes places every 1/50 of a second.

Some of the ideas in this program can be adapted to other programs. Note the modular construction, with each module as small as possible, so that it could be debugged easily during development.

10 REM Oscilloscope V1
20 DIM code 512 set aside area for code
30 PROCassemble assemble code
40 MODE 2 set up screen mode
50 CALL oscilloscope call machine code
60 END
70
80 DEF PROCassemble
90 osbyte = &FFF4 set up variables
100 oswrch = &FFEE constants
110 top = &80 zero page location
120 screen = top + 2
130 temp = screen + 2
140 DIM colour 3,sidebuffer 255 define vectors
150 !colour = &030C0F30 fill colour vector
16O FOR pass = 0 TO 2 STEP 2
170 P% = code
18O[OPT pass
190.osciIloscope entry point
200 JSR clearsidebuffer
210 JSR readchannels
220 JSR vsync
230 JSR scrollscreen
240 JSR writeside
250 JMP oscilloscope
260
270.clearsidebuffer
280 LDA #0 set buffer to 0
290 TAY buffer is a page long
300.clearloop
310 STA sidebuffer,Y
320 DEY
330 BNE clearloop
340 RTS
350
360.readchannels
370 LDX #4 get four chanels
380.loop JSR adval
390 LDA colour-1, X get channel coour
400 STA sidebuffer, Y put reading in buffer
410 DEX get next channel
420 BNE Loop
430 RTS
440
450. vsync
460 LDA # 19 see section 12.1 for
470 jmp osbyte OS 0.10 vsync routine
480
490.scrollscreen
500 LDA top top = 16 bit address
510 STA temp
520 LDA top+1 store top DIV 8 in the
530 LSR A 6845 CRTC chip, see
540 ROR temp section 9.3 for
550 LSR A details of screen
560 ROR temp scrolling
570 LSR A
580 ROR temp
590 LDX #12 register 12 & 13
600 JSR os6845 hold start addres of
610 LDX # 13 memory to be displayed
620 LDA temp
630 JSR os6845
640 # Now increment top
650 CLC adjust variable that
660 LDA top holds start address
670 ADC #8
680 STA top
690 LDA top + 1
700 ADC #0
710 BPL valid address
720 SEC allow for wraparound at
730 SBC #&50 &30000 / &8000 barrier
740.validaddress
750 STA top+1
760 RTS
770
780.writeside dump buffer to screen
790 LDX #&FF start at top of buffer
800 LDY #0 set offset to zero
810 LDA top add &270 to top
820 CLC to right hand side of
830 ADC #&70 the screen
840 STA screen
850 LDA top+1
860 ADC #2
870 BPL validaddress2
880 SEC again allow for
90 SBC #&50 wraparound
900.validaddress2
910 STA screen+1
920.outerloop
930 LDA sidebuffer,X get next byte from buffer
940 STA (screen),Y store to screen
950 DEX adjust buffer pointer
960 INY every 8 bytes down
970 CPY # 8 the program must add in
980 BNE outerloop &280 to the address ,in
990 LDY #0 order to move to the next
1000 LDA screen line
1010 CLC
1020 ADC #&80
1030 STA screen
1040 LDA screen + 1
1050 ADC # 2
1 060 BPL validaddress3
1070 SEC
1080 SBC #&50
1090.validaddress3
1100 STA screen + 1
1110 CPX #&FF buffer all transfered to
1120 BNE outerloop screen ?
1130 RTS
1140
1150.adval
1160 TXA preserve channel pointer
11 70 PHA
1180 LDY # 0 get adval(X)
1190 LDA #&80
1200 JSR osbyte
1210 PLA restore channel pointer
1220 TAX
1230 RTS
1240
1250.os6845
1260 PHA perform
1270 LDA #23 VDU23;X,A,0;0;0;
1280 JSR oswrch to put A into 6845
1290 LDA #0 register X
1300 JSR oswrch
1310 TXA
1320 JSR oswrch
1330 PLA
1340 JSR oswrch
1350 LDX #6
1360 LDA #0
1370.pad
1380 JSR oswrch
1 390 DEX
1400 BNE pad
1410 RTS
1 420]
143O NEXT pass
1440 ENDPROC

  1. Numerical routines
MOD - mod
This routine can be useful when rounding numbers down, or when the remainder of a value is wanted, and the AND instruction cannot be used (e.g. value MOD 3). Note that this method is not one to be recommended for anything other than single byte operations, as the method of repeated subtraction would then be too slow. Also, there is no error checking procedure, so setting Y to 0 would cause the routine to loop forever.
On entry, A holds dividend, and Y holds the divisor. X is irrelevant.

A typical call might be:

LDA #dividend
LDY #divisor
JSR mod

On exit, A holds the remainder, X and Y are preserved.

.mod Performs A = A MOD Y
STY temp SEC Store divisor in temporary location
.modloop
SBC temp Set carry ( for subtraction )
BCS modloop Repeatedly subtract Y from A
ADC temp until A becomes less than zero
RTS Add divisor (Note carry clear) and return

Random number generator - rnd
A routine which is always useful for games programs is a random-number generator. The following routine generates a pseudo-random number in A.
The seed for the random number is three bytes long. This seed can be initialised before using the routine, in order to generate a fixed sequence of numbers. Note that the seed should never contain zero in all three bytes as then the routine will continually give zero.

On entry, all registers are irrelevant. A typical call would be:

JSR rnd

On exit, A holds the next pseudo-random number. X and Y are preserved.

LDA seed Get low byte of shift register
AND #&48
ADC #&38
ASL A
ASL A
ROL seed +2
ROL seed +1
ROL seed
LDA seed
RTS

12.4 Miscellaneous
Cyclic redundancy check - CRC
Cyclic redundancy checks can be useful for error
detection when comparing blocks of data.

Using the program below you can give any block of memory a 'unique' two-byte signature. Thus you can check that two copies of a program are identical, by seeing if they have the same signature. This method is very secure, as it is very unlikely that two different blocks of memory would give the same signature.


0 REM CRC calculator
10
20 signature = &70
30 addr = signature + 2
40 endaddr = addr + 2
50 DIM code 200
60 FOR pass = 0 TO 2 STEP 2
70 P%=code
8O [OPTpass
90.crc
100 LDA # 0 initialise signature
110 STA signature
120 STA signature + 1
130.mainloop
140 JSR crcbyte get crc for each byte
150 INC addr 16 bit increment
1 60 BNE nohibyte
1 70 INC addr + 1
180.nohibyte
190 LA addr if at last address
200 CMP endaddr then end
210 BNE mainloop else do another byte
220 LDA addr + 1
230 CMP endaddr + 1
240 BNE mainloop
250 RTS
260
270.crcbyte
280 LDY #0
290 LDA (addr),Y get byte
300 LDX #8 8 bits in a byte
310.loop
320 LSR A do crc
330 ROL signature
340 ROL signature + 1
350 BCC nextbit
360 PHA
370 LDA signature
380 EOR # &2d
390 STA signature
400 PLA
410.nextbit
420 DEX get new bit in byte
430 BNE loop
440 RTS
450]
460 NEXT pass
470 INPUT “start address &”start$
480 !addr = EVAL (“&”+start$)
490 INPUT “Length &”length$
500 !endaddr EVAL(“&” + length$ + ”+&” + start$)
510 CALL crc
520PRINT “signature = “&”;~!signature AND &FFFF

12.5 General purpose macros
Get low byte - FNlo
DEF FNIo (value) value AND &FF An example call is

LDA #FNIo(table)

Get hi byte -FNhi
DEF FNhi (value) (value AND &FFOO) DIV &100 An example call is

LDY #FNhi(string)

Reserve space - FNspace

DEF FNspace(amount)
P% = P% + amount
0% = 0% + amount this line is only relevant on BASIC II or electron

= pass

An example call is

.table
OPT FNspace(500)

16 bit addition – Fnadc
Provides 16 bit addition.

A typical call might be:

OPT FNadc(&3000, &2000, &2004)

This would add the contents of &2000 (low byte) and &2001 (high byte) to the contents of &3000 (low byte) and &3001 (high byte) and store the result in locations &2004 and &2005.

DEF FNadc(operandl, operand2, result)
[OPT pass
LDA operand1
CLC
ADC operand2
STA result
LDA operand 1+1
ADC operand 1+1
STA result + 1
]
= pass

16 bit subtraction - FNsbc Provides ~6 bit subtraction
A typical call would be

OPT FNsbc(&3000, &2000, &2004)

This would subtract the contents of &2000 (low byte) and &2000 (high byte) from the contents of &3000 (low byte) and &3001 (high byte) and store the result in locations &2004 and &2005.

DEF FNsbc(operand1, operand2,result)
[OPT pass
LDA operand1
SEC
SBC operand2
STA result
LDA operand1+1
SBC operand2+1
STA result+1
]

= pass

Debugging macro - ENdebug

This can be inserted anywhere in the sources code to provide an indication of which path the processor has taken through the program. When executed, the routine will make a 'Bleep' sound and wait for a key to be pressed before continuing. All registers are preserved.

A typical call would be
OPT FNdebug

DEF FNdebug
[OPT pass
PHP Save all registers
PHA
TYA
PHA
TXA
PHA
LDA #7 Make 'Bleep' sound
J SR oswrch
LDX #1 Flush keyboard buffer
LDA #15
JSR osbyte
JSR osrdch Wait for a key
PLA Restore all registers
TAX
PLA
TAY
PLA
PLP
]

= pass

16 bit rotation - FNshift
This provides a 16-bit shift instruction.

A typical call might be

OPT FNshift(&2034, TRUE, 2)

which would shift locations &2034 and &2035 right twice.

DEF FNshift(addr, right, number)
LOCAL shift
FOR shift 1 TO number
IF right [OPT pass :LSR addr +1 :ROR addr :]
ELSE [OPT pass ASL addr: ROL addr + 1: ]
NEXT shift
= pass

12.6 BASIC routines for use with assembler Double height - PROCdouble

The next two routines can be used to produce double height characters in MODEs 0,1,2,4 and 5. MODEs 3 and 6 (and 7 on the Acorn Electron) have gaps between lines which make it impossible to do double height. To centre the String, type
'PRINT TAB ((screenwidth DIV 2) - LEN(A$) DIV 2,VPOS);
after 'LOCAL I%'. Note that 'block' is a global array which should be DIMensioned at the start of the program, using, for example, DIM block 9. Note also that 'char' is the character to be defined, in this case it is always 224. The routine currently prints out the characters as it redefines them, although it is possible to suppress this.

DEFPROCdouble (A$)
LOCAL I%
FOR I% = 1 TO LEN(A$)
PROCchar (ASC (MID$ (A$,I% , 1)),224)
NEXT I%
ENDPROC

DEFPROCchar (C%,char)
LOCAL A%,X%,Y%,J%,I%
?block = C%
A% = 10
X% = FNIo(block)
Y% = FNhi (block)
CALL osword osword is at &FFF1
FOR J% = 0 TO 1
VDU 23, char
FOR I% = 2 TO 9
VDU block?(J% * 4 + I% DIV 2)
NEXT I%
VDU char,10,8
NEXT J%
VDU 11,11,9
ENDPROC

Find string in program - FROCfind
This next routine will find all occurrences of a specified string in the BASIC program, and print out the line numbers in which the string occurs. In its present form, the routine will not find BASIC keywords (FOR, REPEAT, PROC, etc). To allow for this it will be necessary to store the string as a line of BASIC, which could then be used as the target string in the search. So type PROCfind ($(PACE + 4)). This will find the string specified in the first line of the program, which should be line 0 to avoid the routine searching for the wrong string. The first line of the routine can now be changed to 'DEFPROCfind', as the parameter is now in line 0. Note that the first IF statement is only needed in BASIC I.

DEFPROCfind (A$)
LOCAL Z%,A%
Z% = PAGE
REPEAT A% = Z% + 4
IF LEN ($A%) >= LEN (A$) IF INSTR($A%,A$)
PRINT Z%?1 * 256 + Z%?2 ; Z% = Z% + Z%?3 UNTIL Z%?1 > &7F
ENDPROC

13 GRAPHICS

This chapter is all about fast shape drawing. Like everything else, shapes such as space invaders are stored as a series of numbers in the computer's memory This chapter consists of two main sections. The first contains a routine which is designed to be used independent of any other program. It allows a shape to be designed, and produces the relevant numbers representing that shape. The second contains two routines which are meant to be included as part of an assembler program. While the program is running, these routines convert the numbers in the memory back into the original shape and plot this shape on the screen at a stated position. These routines have been highly optimised for fast animation.

Some knowledge of graphics is assumed, up to User Guide level. Those of you without this knowledge are recommended to read the Acornsoft book, Creative Graphics, which gives a clear illustration of the graphics facilities available on the BBC Microcomputer and Acorn Electron.

13.1 Shape designer - DESIGN
The BASIC program below can be used to design hapes which can be plotted on the screen using the routines described later in this chapter. Remember that smaller shapes are plotted faster so if vou wish to move several shapes on the screen at once it is better not to make them too large. The program is only suitable for a BBC Model B, or Electron.

The keys used by this program are:

0,1..... E,F Colours 0 to 15
V and H Vertical and horizontal reflections
L and S Load and Save shapes
Cursor keys For moving the cursor around
SY% Height of 'pixels' in large shape.
shape Byte vector to hold shape.
cursor X X co-ordinate of cursor in 'pixels'.
cursor Y Y co-ordinate of cursor in 'pixels'.
command$ String containing commands.
String containing keys for colours.
Colour $ Colours are specified by presssing key
corresponding to Hexadecimal digit.
Note state of CAPS LOCK or SHIFT
keys is irrelevant.
key$ Holds key pressed.
length Holds length of shape data.
C% Zero if command, else holds colour
key plus one.

0 REM Design Program
10
---------------------------------------------------------------------------
Top Level. Note that FNinitialise returns the mode
as MODEs cannot be defined in FNs or PROCs
---------------------------------------------------------------------------
20 MODE FNinitialise
30 PRoCmainloop
40
--------------------------------------------------------------------------
Set up global variables, especially those that are
relevant to the screen mode selected.
--------------------------------------------------------------------------
50 DEF FNinitialise
60 lenshape 300
70 DIM shape lenshape
80 REPEAT
90 INPUT “Mode ( 0 – 2 ) ?”mode
100 UNTiLmode >0 AND mode <=2
110 RESTORE 230
120 FOR I% = 0 TO mode
130 READ pixbits
140 NEXT I%
150 RESTORE 240
160 FOR I% = 0 TO mode
170 READ W%
180 NEXT I%
190 RESTORE 250
200 FOR I% = 0 TO mode
210 READ pixelperbyte
220 NEXT I%
230 DATA 1, 2, 4
240 DATA 2, 4, 8
250 DATA 8, 4, 2
260 INPUT "Width in X-direction:" NX%
270 INPUT "Width in Y-direction:" NY%
280 byteNX% = NX%DlVpixelperbyte
290 byteNY% = NY%-1
300 SX%= (1024 DIV NX%) AND &FFFO
310 SY% = (1024 DIV NY%) AND &FFF8
320 PROCclear
330 cursorX = 0 : cursorY = 0
340 *FX 4 1
350 command$ = "VvHhLlSs" + CHR$&88 + CHR$&89 + CHR$&8A + CHR$&8B
360 colour$ = "001!2""3#4$5%6&7'8(9)AaBbCcDdEeFf"
370 Length = NX% * NY% * pixbits DIV 8
380 = mode
390
-------------------------------------------------------------------------------------------------------------------------
Main loop. Get a key, and then test to see if it is legal. If the key is legal, then pass it on to
the rest of the routine, which then calls the relevant routine(s).
-------------------------------------------------------------------------------------------------------------------------
400 DEF PRoCmainloop
410 PROCcursor
420 REPEAT
430 REPEAT
440 keyS = GETS
450 UNTILINSTR(colour$,key$) OR INSTR(command$,key$)
460 C% = (INSTR(colour$, key$) + 1) DIV 2
470 IF C% THEN PROCcolour(C%-1) ELSE ON
INSTR(commandS, key$) GOSUB 670, 670, 780, 780,
630, 630, 650, 650, 510, 540, 570, 600
480 UNTIL FALSE
490 ENDPROC
500
---------------------------------------------------------------------------------------------------------------------------
Handle cursor control keys, makinq sure that the cursor does not go off the side of the screen.
---------------------------------------------------------------------------------------------------------------------------
510 IF cursorX > 0 THEN PRCCdocursor(-1,0)
520 RETURN
530
540 IF cursorX < NX% - 1 THEN PRoCdocursor(1,0)
550 RETURN
560
570 IF cursorY > 0 THEN PR0Cdocursor(0,-1)
580 RETURN
590
600 IF cursorY < NY% - 1 THEN PR0Cdocursor(0,1)
610 RETURN
620
---------------------------------------------
Handle saving/loading of shapes
---------------------------------------------
630 PROCload : VDU 22, mode PR0Cshape(FALSE,byteNX%,byteNY%): PR0cdisplay : RETURN 640
650 PROCsave : VDU 22, mode by teN X% , byt eN Y%) :PROCshape(FALSE, PR0Cdisplay : RETURN
660
---------------------------------------------------------
Reflect the shape in Y-maximum Y / 2
----------------------------------------------------------
670 FORI%--0 TO NX%-1
680 FORJ%=0 TO (NY%-1)DIV2
690 temp=FNpoint (I%,J%)
700 PROCplot(I%,J%,FNpoint(I%,NY%-1-J%))
710 PROCplot(I%,NY%-1-J%,temp)
720 NEXTJ%
73C NEXTI%
740 PROCshape (TRUE,byteNX%,byteNY%)
750 PROCdisplay
760 RETURN
770
-----------------------------------------------------
Reflect the shape in X maximum X / 2
------------------------------------------------------
780 FORJ%=0 TO NY%-1
790 F0RI%=0 TO (NX%-1)DIV2
800 temp=FNpoint(I%,J%)
810 PROCpIot(I%,J%,FNpoint (NX%-1-I%,J%) )
820 PROCplot (NX%-1-I%,J%,temp)
830 NEXTI%
840 NEXTJ%
850 PROCshape (TRUE ,byteNX%,byteNY%)
860 PRoCdisplay
870 RETURN
880
890 DEF PROCplot(X,Y,col)
900 GCOL 0,col
910 PLOT 69, X * W% + 1024, Y * 4 + 640
920 ENDPROC
930
940 DEF FNpoint(X,Y)
950 = POINT(X * W% + 1024, Y * 4 + 640)
960
----------------------------------------------------------------------
Plot a large square at the cursor position, looking
at the final size version at the side of the screen
to find the color
----------------------------------------------------------------------
970 DEF PROCsq(X, Y)
980 GCOL 0, FNpoint(X,Y)
990 MOVE X * SX%, Y * SY%
1000 PLOT 0, SX% - 4, 0
1010 PLOT 81, 4 – SX%, SY% - 4
1020 PLOT 81, SX% - 4, 0
1030 ENDPROC
1040
-------------------------------------------------------------------
Display whole shape
--------------------------------------------------------------------
1050 DEF PROCdisplay
1060 LOCAL X, Y
1070 FOR X = 0 TO NX% - 1
1080 FOR Y = 0 TO NY% - 1
1090 PROCsa(X,Y)
1100 NEXT Y
  1. NEXT X
  2. PROC cursor
1130 ENDPROC
1140
-------------------------------------------------------------------
plot the box cursor at the cursor position
-------------------------------------------------------------------
  1. DEF PROCcursor
1160 VDU 5
1170 GCOL 3,3
1180 MOVE SX% * (cursorX + 0.25), SY% * (cursorY + 0.25)
1190 PLOT 1, SX% DIV 2 – 4 , 0
1200 PLOT 1 , 0 , SY% DIV 2 - 4
  1. PLOT 1 , 4 – SX% DIV 2 , 0
  2. PLOT 1 , 0 , 4 – SY% DIV 2
1230 ENDPROC
1240
---------------------------------------------------------------------
get a shape from the filing system
------------------------------------------------------------------
1250 DEF PROCload
1260 LOCAL I%, channel
1270 PROCshape(TRUE, byteNX%, byte NY%)
1280 channel = FNopen(TRUE)
1290 IF channel = FALSE ENDPROC
1300 PROCclear
1310 FOR I% = 0 TO (byteNX% * NY%)-1
1320 SHAPE?I% = BGET#channel
1330 NEXT I%
1340 CLOSE #channel
1350 ENDPROC
1360
----------------------------------------------------------------
write a shape to the filing system
---------------------------------------------------------------
1370 DEF PROCsave
1380 LOCAL I% , channel
1390 PROCshape (TRUE, byteNX% ,byte NY%)
1400 channel = FNopen(FALSE)
1410 IF channel = FALSE ENDPROC
1420 FOR I% = 0 TO (byteNX%*NY%) -1
1430 BPUT#channel,shape? I%
1440 NEXT I%
1450 CLOSE #channel
1460 ENDPROC
1470
---------------------------------------------------------------------
utility used by procload and procsave to open a file
---------------------------------------------------------------------
1480 DEF FNopen(in)
1490 LOCAL W$
1500 VDU 22,7
1510 INPUT "file name ?"W$
1520 IF W$ = "" FALSE
1530 IF in THEN OPENIN(W$) ELSE OPENOUT(W$)
1540
------------------------------------------------------------------------
plot point on final size shape and also plot square on main screen
------------------------------------------------------------------------
1550 DEF PROCcolour (colour)
1560 PROCcursor
1570 PROCplot(cursorX,cursorY,colour)
1580 PROCsq(cursorX,cursorY)
1590 PROCcursor
1600 ENDPORC
1610
----------------------------------------------------------------------
Wipe previous cursor from screen update cursor's X and Y coordinates, and then plot the cursor at new coordinates
-------------------------------------------------------------------------
1620 DEF PROCdocursor(X,Y)
1630 PROCcursor
1640 cursorX = cursorX + X
1650 cursorY = cursorY + Y
1660 PROCcursor
1670 ENDPROC
1680
--------------------------------------------------------------------------
clear the array used to hold the shape
-------------------------------------------------------------------------
1690 DEF PROCclear
1700 LOCAL clear
1710 FOR clear = 0 TO lenshape - 4 STEP 4
1720 clear!shape=0
1730 NEXT clear
1740 ENDPROC
1750
-------------------------------------------------------------------------
either write final size shape from shape array or put final size shape into shape array
---------------------------------------------------------------------------
1760 DEFPROCshape(flag, X, Y)
1770 LOCAL I%, J%, tempx, tempy
1780 FOR I% = 0 TO X
1790 FOR J% = 0 TO Y
1800 tempx=I%+1024 DIV 16
1810 tempy=((J%+640 DIV 4)EOR&FF)DIV8
1820 PROCaccess(flag, (tempy*&280+tempx*8+((J%
AND 7)EOR 7)+&3000), Y+1, J%, I%)
1830 NEXT J%
1840 NEXT I%
1850 ENDPROC
1860
---------------------------------------------------------------------------------
Get/put byte from/to final shape.
---------------------------------------------------------------------------------
1870 DEF POCaccess(flag,addr , Y , J% , I%)
1880 IF flag THEN shape/(I% * Y + ( Y - J% - 1 )) = ?addr ELSE
?addr=shape? (I%*Y+ (Y-J%-1) )
1890 ENDPROC

Variables Used:

lenshape Maximum length of shape (in bytes).
mode Holds graphics modeseected.
I% generral loop control variable.
J% general loop control variable.
pixbits Number of bits per pixel.
pixelperbyte holds number of pixels per byte.
W% Width of pixels in graphics co-ordinates
NX% Width of shape
NY% height of shape
byteNX% Width of shape (in bytes)
byteNY% height of shape (in bytes)
SX% Width of pixels In larga shape
SY% height of pixels in large shape.
shape Byte vector to hold shape.
cursorX X co-ordinate of cursor in pixels.
cursorY Y co-ordinate of cursor in 'pixels'.
command$ String containing commands.
colour$ String containing keys for colours
Colours are spiecified by pressing key
corresponding to Hexadecimal digit.
Note state of CAPS LOCK or SHIFT
keys is irrelevant.
key$ Holds key' pressed.
length Holds length of shape data.
C% Zero if command, else holds colour key plus one.

3.2 Plotting a shape on the screen

To plot a shape on the screen at a specified position it is necessary to have two routines; one to convert the X and Y coordinates to a memory location on the screen, and another routine to plot a shape at that address.

Two routines are given below which perform these tasks. The method chosen can only be used for plotting shapes to a resolution of 80 by 256 in MODEs 0, 1 and 2. The routines work by Exclusive ORing the shape onto the screen. This has two main advantages over other methods (such as writing the shape on the screen or ORing the shape with the screen memory). The first advantage is that the detail under the shape is not lost when the shape is unplotted, the second is that the same routine can be used for both plotting and unpotting.

Convert X, Y coordinate to screen address - getaddr
This routine doesn't write anything to the screen, all that it does is generate an address where a shape might then be written to the screen, or read from the screen. It will generate an address for MODEs 0, 1 and 2, allowing for hardware scroll. The algorithm used is given at the end of the listing so that the code can be adapted for MODEs 4 and 5.

On entry, X holds the X coordinate (0 79), Y holds the Y coordinate (0 - 255), and A is irrelevant.

A typical call might be:

LDX xcoord
LDY ycoord
JSR getaddr

On exit, X will be preserved, Y and A will have been corrupted. The resultant address is left in 'addr' and 'addr+1' (low byte, high byte) which must be in zero page. Other locations used are 'temp' (1 byte) and 'top' (2 bytes). For speed, these locations should also be in zero page.

.getaddr
LDA #&00 set hi byte of address
STA addr + 1 to zero
TYA
EOR #&FF invert Y coordinate
PHA
OPT FN rotateacc(3) divide Y cordinate by 8
TAY and leave in Y
LSR A adjust carry for * &280
STA temp save Y/16 in temp
LDA #&00 set bottom byte of address to 0
ROR A put carry into top bit
ADC top and add in top of screen
PHP save carry flag
STA addr store result in addr
TYA get y/8
ASL A double it for top byte
ADC temp of addr .add in Y/16
PLP restore carry
ADC top + 1 and add in top of screen
STA addr +1
LDA #&00 set temp to zero
STA temp
TXA get x coordinate
ASL A perform two byte multiply
ROL temp by 8 because of memory
ASL A map
ROL temp
ASL A
ROL temp
ADC addr add in rest of result so
STA addr far, and store it
LDA temp
ADC addr+1
BPL ok check for hardwar scroll
SEC if over 3000 - 8000 boundary
SBC #&50 then correct address
.ok
STA add r+1 And store it
PLA Restore inverted Y coord
AND #&07 Get row number in computed
ORA addr column, and add it in
STA addr
RTS Return

Some words of explanation:

The algorithm used to calculate the screen memory address is:

addr = X * 8 + ((Y EOR &FF) AND 7) +&280 * ((Y EOR &FF)DIV 8)+ top

where X and Y are the coordinates. The reason Y is inverted is so that the bottom of the screen is treated as 0, even though the memory map is the other way round. There are 640 bytes per character line on the display, which is &280 in hex. The variable 'top' is normally set to &3000, except when hardware scroll is taking place. In most applications this will be irrelevant, and so 'top' may be set up at the beginning of the program and then forgotten about. The Y AND 7 and Y DIV 8 operations are performed because of the memory map of the screen, DIV 8 is performed to get to the start of the current character cell, and AND 7 to get to the current byte in the character cell:

top +0 +8 +&10 +&18 +&20 +&28
+0 +------- +------- +------- +------- +------- +-------
+1 +------- +------- +------- +------- +------- +-------
+2 +------- +------- +------- +------- +------- +-------
+3 +------- +------- +------- +------- +------- +-------
+4 +------- +------- +------- +------- +------- +-------
+5 +------- +------- +------- +------- +------- +-------
+6 +------- +------- +------- +------- +------- +-------
+7 +------- +------- +------- +------- +------- +-------
+&280 +------- +------- +------- +------- +------- +-------
+&281 +------- +------- +------- +------- +------- +-------

Plotting a shape at a given screen coordinate - doplot

This routine works in conjunction with the above routine for plotting a shape at a given X, Y screen coordinate.

On entry, all registers are irrelevant. The parameters passed are:

counter - holds number of bytes in the shape
addr - holds screen address to put shape
depth - holds height of shape
shape - start address of shape in memory

('shape' must be in zero page.)

On exit, all registers may have been corrupted, but all parameters will have been preserved.

.doplot
LDY #&00 set shape offset to zero
LDA addr+1 push screen sddress onto
PHA stack for later use
LDA addr
PHA
LDA depth get depth of shape
STA rowcounter
LDA addr put offset in character
AND #&07 cell into Y
STA offset
LDA addr adjust address accordingly
AND #&F8 goto top of character cell
STA addr
STY temp
.innerloop
LDY temp
LDA (shape),Y get byte from shape
INY
STY temp
LDY offset
EOR (addr),Y
STA (addr),Y
INY Y holds offset on screen
CPY #&08 bottom of character cell
BEQ block if so then go down a line
.nobIoc
STY offset
DEC rowcounter
BNE innerloop
.nextblock
LDA shape
CLC
ADC depth
STA shape
BCC nohi
INC shape+1
.nohi
CLC go to the top of the next column
PLA by resetting address to top
ADC #&08 of current column, and
STA addr moving to nect character
PLA cell
ADC #&00
BPL nobound1
SEC
SBC #&50
.nobound1
STA addr+1
DEC counter easier to DEC counter in
BNE doplot two place and test
RTS
.block
LDY #&00 go down a line
LDA addr addr = (addr + &280)
CLC
ADC #&80
STA addr
LDA adr + 1
ADC #&02
BPL noboundary if the contents of addr are
SEC greater than &8000 then
SBC #&50 subtract &5000
.noboundary
STA addr + 1
BNE nolock always jump

Interfacing getaddr and doplot - plotshape

The number of parameters may be cut down by having a 'front end' attached to the start of the doplot routine which would make interfacing easier.
On entry to 'plotshape' A would hold the number of the shape to be plotted and X and Y would hold the X and Y coordinates at which the shape is to be plotted.

A typical call to 'plotshape' would be:

LDY #0 Get first shape
TYA
PHA preserve shape number
LDA xcoord, Y get x coordinate
TAX
LDA ycoord, Y get y coordinate
TAY
PLA get shape number back
JSR plotshape plot shape

On exit from 'plotshape' all registers may have been corrupted, although all internal parameters ('depth', 'counter', etc.) will have been preserved.

.plotshape
PHA Save shape number
JSR getaddr Get address
PLA Restore shape
TAY
LDA shapeloaddr, Y Set up parameters
STA shape This assumes that
LDA shapehiaddr, Y the User has set
STA shape + 1 up the relevent
LDA shapesize ,Y tables
STA counter (shape loaddr,
LDA shapedepth, Y shapesize, etc)
STA depth