Other books for BBC Micro users
Introducing the BBC Micro
Ian Sinclair
0 00 383072 1
The BBC Micro: An Expert Guide
Mike James
0 246 12014 2
Discovering BBC Micro Machine Code
A. P. Stephenson
0 246 12160 2
Advanced Machine Code Techniques for the BBC Micro
A. P. Stephenson and D. J. Stephenson
0 246 12227 7
BBC Micro Graphics and Sound
Steve Money
0 246 12156 4
Practical Programs for the BBC Micro
Owen Bishop and Audrey Bishop
0 246 12405 9
21 Games for the BBC Micro
Mike James, S. M. Gee and Kay Ewbank
0 246 12103 3
Disk Systems for the BBC Micro
lan Sinclair
0 246 12325 7
Learning is Fun -
40 Educational Games for the BBC Micro
Vince Apps
0 246 12317 6
Advanced Programming for the BBC Micro
Mike James and S. M. Gee
0 00 383073 X
Take Off with the Electron and BBC Micro
Audrey Bishop and Owen Bishop
0 246 12356 7
Creative Animation and Graphics for the BBC Micro
Mike James
0 00 383007 1
Handbook of Procedures and Functions for the BBC Micro
Audrey Bishop and Owen Bishop
0 245 12415 6
GRANADA
London Toronto Sydney New York
Granada Technical Books
Granada Publishing Ltd
8 Grafton Street, London W1X 3LA
First published in Great Britain by
Granada Publishing 1984
Distributed in the United States of America
by Sheridan House, Inc.
Copyright © 1984 Bruce Smith
British Library Cataloguing in Publication Data
Smith, Bruce The BBC Micro machine code portfolio
I . Microcomputer-Programming
I. Title
00l.64'24 QA76.8.B35
ISBN 0-246-12643-4
Typeset by V & M Graphics Ltd, Aylesbury, Bucks
Printed and bound in Great Britain by
Mackays of Chatham, Kent
All rights reserved. No part of this publication may
be reproduced, stored in a retrieval system or
transmitted, in any form, or by any means, electronic,
mechanical, photocopying, recording or otherwise,
without the prior permission of the publishers.
DIGITALLY REMASTERED ON ACORN RISC OS COMPUTERS, JANUARY 2007
Contents
Acknowledgements vii
1 Introduction 1
2 Function Key Reader 10
3 Program Information 20
4 Program Formatters 32
5 The Screen 41
6 Softly, Softly 53
7 Global Variable Search and Replace 61
8 Time for Bed 72
9 Error, Pack and Autorun 78
10 The Necessary Evil 88
11 Vision On 108
12 Assembling Data and Lists 131
13 Communication 165
14 Odd One Out 183
Index 210
*SPOOL NAME
If you are using the cassette filing system then you'll need to start the cassette running. If you are using disks then these will already be whirling around. So, now simply LIST the program. As the listing appears onto the screen it will also be written to the current filing system. When the program has finished listing enter
*SPOOL
once again to complete the transfer from memory to filing medium. You will probably have noticed that the filename used in the *SPOOL command was not enclosed within the quotes normally associated with a SAVE. This is acceptable as the MOS does not expect them - though using them will have no adverse effect.
Once all programs have been treated in this way they are ready to be used in a greater scheme of things! One point - when building up a large library of procedures it is very important to catalogue them, in a book, on another disk or tape, or on the front of each tape or disk. This catalogue should depict the program's name, line numbers and function as this will be invaluable when it comes to using them at a later date.
Once a program has been saved as a spooled file it can be loaded back into memory using the MOS-based *EXEC command. To load the previously spooled file you use
*EXEC NAME
Again, the quotes around the filename are not obligatory. When the operating system encounters the file it treats each line of it as though it had been typed in at the keyboard and as the return character at the end of the line is reached the line is entered into memory. This is the main reason for using unique line numbers within procedures, as it enables several procedures to be *EXECuted into memory without fear of overwriting any program lines already there.
*EXEC mode
*EXEC move
*EXEC plot
The resultant listing forms part of Program 1.1. Next, a BASIC primer needs to be written to call each PROC and pass the relevant information through the arguments of the procedural call. First, PROCmode:
20 PROCmode (4,&A96)
The PROCedure is called passing the mode number, 4 into the variable
'action' and the assembly address, &A99, into 'addr'. Next, the graphics cursor must be moved. The problem here is that we do not really want to have to calculate the new value to be assigned to 'addr' for the code assembly; instead we can simply use the program counter itself in the form of P%. Thus line 30 becomes
30 PROCmove (200,200,P%)
The move coordinates are 200,200 and the PROCmove code is assembled from P%. Finally PROCplot can be treated in the same way to give
40 PROCplot (21,900,600,P%)
where 21 is the plot code for an absolute dotted line, 900,600 the final coordinates and P% the assembly address. Now each PROCedure will assemble its code as a subroutine call. To implement the machine code, a short procedure must be written that will call each subroutine in turn, thus:
JSR mode \ set up MODE
JSR move \ move graphics cursor
JMP plot \ draw line and return
Program 1.1 lists the final program and, by way of proof of the output, Figure 1.1 lists the assembler listing produced when RUN.
When using this modular-cum-structured assembly approach, the use of the OPT command must be borne in mind. If the OPT command is omitted then the default value of 3 will be assumed by the assembler. In the case of the above example this was not too much of a problem, but there are occasions when it will be! For example, if assembly is performed on a conditional basis then it may be desirable to suppress it altogether using OPT 2 lest it corrupt some vital screen detail. Alternatively, a FOR . . . NEXT combination may be imperative to suppress errors during a first pass to assign forward branch labels. There is no simple way around this. A universal solution would be to include a
FOR pass=0 TO 2 STEP 2
line in all procedures. I prefer to add the OPT commands as required, but the choice is yours.
10 REM *** USING THE PORTFOLIO *** 20 PROCmode (4,&A00) 30 PROCmove (200,200,P%) 40 PROCplot (21,900,600,P%) 50 PROCassemble(P%) 60 CALL test 70 END 80 : 100 DEF PROCassemble (addr) 110 P%=addr 120 [ 130 .test 140 JSR mode 150 JSR move 160 JMP plot 170 ] 180 ENDPROC 200 : 6000 DEF PROCmode (action,addr) 6001 P%=addr 6002 [ 6003 .mode 6004 LDA #22 6005 JSR &FFEE 6006 LDA #action 6007 JSR &FFEE 6008 RTS 6009 ] 6010 ENDPROC 6180 DEF PROCmove(xpos,ypos,addr) 6181 P%=addr 6182 [ 6183 .move 6184 LDA #25 6185 JSR &FFEE 6186 LDA #4 6187 JSR &FFEE 6188 LDA #xpos MOD 256 6189 JSR &FFEE 6190 LDA #xpos DIV 256 6191 JSR &FFEE 6192 LDA #ypos MOD 256 6193 JSR &FFEE 6194 LDA #ypos DIV 256 6195 JSR &FFEE 6196 RTS 6197 ] 6198 ENDPROC 6220 DEF PROCplot(code,xcoord,ycoord,ad dr) 6221 P%=addr 6222 [ 6223 .plot 6224 LDA #25 6225 JSR &FFEE 6226 LDA #code 6227 JSR &FFEE 6228 LDA #xcoord MOD 256 6229 JSR &FFEE 6230 LDA #xcoord DIV 256 6231 JSR &FFEE 6232 LDA #ycoord MOD 256 6233 JSR &FFEE 6234 LDA #ycoord DIV256 6235 JSR &FFEE 6236 RTS 6237 ] 6238 ENDPROC
Program 1.1. Spooling procedures to form a graphics program
>RUN 0A00 0A00 .mode 0A00 A9 16 LDA #22 0A02 20 EE FF JSR &FFEE 0A05 A9 04 LDA #action 0A07 20 EE FF JSR &FFEE 0A0A 60 RTS 0A0B 0A0B .move 0A0B A9 19 LDA #25 0A0D 20 EE FF JSR &FFEE 0A10 A9 04 LDA #4 0A12 20 EE FF JSR &FFEE 0A15 A9 C8 LDA #xpos MOD 256 0A17 20 EE FF JSR &FFEE 0A1A A9 00 LDA #xpos DIV 256 0A1C 20 EE FF JSR &FFEE 0A1F A9 C8 LDA #ypos MOD 256 0A21 20 EE FF JSR &FFEE 0A24 A9 00 LDA #ypos DIV 256 0A26 20 EE FF JSR &FFEE 0A29 60 RTS 0A2A 0A2A .plot 0A2A A9 19 LDA #25 0A2C 20 EE FF JSR &FFEE 0A2F A9 15 LDA #code 0A31 20 EE FF JSR &FFEE 0A34 A9 84 LDA #xcoord MOD 256 0A36 20 EE FF JSR &FFEE 0A39 A9 03 LDA #xcoord DIV 256 0A3B 20 EE FF JSR &FFEE 0A3E A9 58 LDA #ycoord MOD 256 0A40 20 EE FF JSR &FFEE 0A43 A9 02 LDA #ycoord DIV256 0A45 20 EE FF JSR &FFEE 0A48 60 RTS 0A49 0A49 .test 0A49 20 00 0A JSR mode 0A4C 20 0B 0A JSR move 0A4F 4C 2A 0A JMP plot
Fig. 1.1. Assembler listing produced by Program 1.1.
EQUB | : assemble specified byte |
EQUW | : assemble specified word (2 bytes) |
EQUD | : assemble specified double word (4 bytes) |
EQUS | : assemble specified string as ASCII characters |
10 REM ** SIMULATING BASIC II EQU ** 20 P%=&900 30 [ 40 LDA #255 50 OPT FNequs("TEST",3) 60 LDX #0 70 OPT FNequb(6,3) 80 LDY #&33 90 OPT FNequw(&FFFF,3) 100 STX &70 110 OPT FNequd(&12345678,3) 120 LDX #&AA 130 RTS 140 ] 150 END 160 : 500 DEF FNequs(string$,opt) 510 $P%=string$ 520 P%=P%+LEN(string$) 530 =opt 540 : 550 DEF FNequb(byte%,opt) 560 ?P%=byte% 570 P%=P%+1 580 =opt 590 : 600 DEF FNequw(word%,opt) 610 ?P%=word% MOD 256 620 P%?1=word% DIV 256 630 P%=P%+2 640 =opt 650 : 660 DEF FNequd(double%,opt) 670 !P%=double% 680 P%=P%+4 690 =opt
Program 1.2. Simulating the BASIC II EQU functions in BASIC I.
The assembler text (lines 40 to 130) shows how each procedure should be called. The second parameter in each of the OPT FN calls (3 throughout) simply refers to the OPT selection and this should be seeded as required by the program. To end with, Figure 1.2 shows the assembler listing provided when running this program, while the hex dump in Figure 1.3 shows that each FN has indeed performed the required task.
>RUN 0900 0900 A9 FF LDA #255 0906 OPT FNequs("TEST",3) 0906 A2 00 LDX #0 0909 OPT FNequb(6,3) 0909 A0 33 LDY #&33 090D OPT FNequw(&FFFF,3) 090D 86 70 STX &70 0913 OPT FNequd(&12345678,3) 0913 A2 AA LDX #&AA 0915 60 RTS
Fig. 1.2. Assembler listing produced by Program 1.2
900 A9 901 FF 902 54 903 45 904 53 905 54 906 A2 907 0 908 6 909 A0 90A 33 90B FF 90C FF 90D 86 90E 70 90F 78 910 56 911 34 912 12 913 A2 914 AA 915 60
Fig. 1.3. A hex dump of the code assembled by Program 1.2, showing that the functions have worked.
Key | Pointer Byte |
0 | &B00 |
1 | &B01 |
2 | &B02 |
3 | &B03 |
4 | &B04 |
5 | &B05 |
6 | &B06 |
7 | &B07 |
8 | &B08 |
9 | &B09 |
10 | &B0A |
11 | &B0B |
12 | &B0C |
13 | &B0D |
14 | &B0E |
15 | &B0F |
0B00 10 10 10 10 10 10 10 10 ........ 0B08 10 10 10 10 10 10 10 10 ........ 0B10 10 10 10 10 10 10 10 10 ........ 0B18 10 10 10 10 10 10 10 10 ........ 0B20 10 10 10 10 10 10 10 10 ........ 0B28 10 10 10 10 10 10 10 10 ........ 0B30 10 10 10 10 10 10 10 10 ........ 0038 10 10 10 10 10 10 10 10 ........
Fig 2.2. Key buffer after switch-on.
Figure 2.3 depicts the same area of the key buffer after a short
definition has been entered into f0 thus:
*KEY 0 CLS |M
0B00 10 14 14 14 14 14 14 14 ........ 0B08 14 14 14 14 14 14 14 14 ........ 0B10 14 43 4C 53 0D 10 10 10 .CLS.... 0B18 10 10 10 10 10 10 10 10 ........ 0B20 10 10 10 10 10 10 10 10 ........ 0B28 10 10 10 10 10 10 10 10 ........ 0B30 10 10 10 10 10 10 10 10 ........ 0B38 10 10 10 10 10 10 10 10 ........
Fig 2.3. Key buffer after executing *KEY0 CLS|M.
The dump shows that the ASCII string CLS is present but that the return sequence '|M' has been replaced with the more conventional ASCII return character &0D. It is also obvious from the dump that the key pointer bytes have altered. The first byte at &B00 is, we know from Figure 2.1, associated with *KEY9, and this byte still contains &10 or 16 decimal. Counting sixteen bytes from this location we arrive at the first character in the *KEY 0 definition. The remaining key pointer bytes now all contain &14 or 20 decimal; counting 20 bytes from the *KEY 0 pointer brings us to the last byte; of the *KEY 0 definition, the carriage return character at &B14.
Figure 2.4 shows the buffer after a further key has been defined, thus:
*KEY 9 AUTO|M
0B00 10 19 19 19 19 19 19 19 ........ 0B08 19 14 19 19 19 19 19 19 ........ 0B10 19 43 4C 53 0D 41 55 54 .CLS.AUT 0B18 4F 0D 10 10 10 10 10 10 O....... 0B20 10 10 10 10 10 10 10 10 ........ 0B28 10 10 10 10 10 10 10 10 ........ 0B30 10 10 10 10 10 10 10 10 ........ 0B38 10 10 10 10 10 10 10 10 ........
Fig. 2.4. Key buffer after executing *KEY9 AUTO|M.
The ASCII characters of the new definition are entered into the buffer immediately after the last definition. The key pointer byte for *KEY 9 at &B09 still contains & 14 while the *KEY9 byte remains at &10. All the other key pointer bytes have been updated to hold &19 or 25 decimal. Starting from &B00 and counting 25 bytes brings us to &B19, the last byte defined in the buffer.
It is worth looking at what happens in the buffer if a function key is redefined. Figure 2.5 shows the effect of placing a longer definition into *KEY 0 than was already present, thus:
*KEY 0 VDU 7|M
What has happened now is that the previous *KEY0 definition has been deleted, the remaining definition(s) shuffled up to the front of the buffer and the new *KEY0 definition added onto the end. Each of the key pointer bytes have been adjusted to point to the correct location.
0B00 15 1B 1B 1B 1B 1B 1B 1B ........ 0B08 1B 10 1B 1B 1B 1B 1B 1B ........ 0B10 1B 41 55 54 4F 0D 56 44 .AUTO.VD 0B18 55 20 37 0D 10 10 10 10 U 7..... 0B20 10 10 10 10 10 10 10 10 ........ 0B28 10 10 10 10 10 10 10 10 ........ 0B30 10 10 10 10 10 10 10 10 ........ 0B38 10 10 10 10 10 10 10 10 ........
Fig. 2.5. Key buffer after redefining f0 as *KEY0 VDU 7|M
*KEY 9 was defined as AUTO and the pointer byte at &B09 now holds &10 giving the offset from &B00 to the start of the definition. *KEY 0 which is now tacked onto the end of the *KEY 9 definition has had its pointer offset reset to &15 or 21 decimal. The remaining pointer bytes have also been adjusted to all give the correct offset to the last used byte in the buffer, &1B or 27 decimal, which when added to &B00 gives &B1B.
The key pointer area contains an extra 'general' byte at &B10 that we have not yet mentioned. This byte is, in fact, the TOP pointer in the buffer and always holds the byte offset into the buffer of the last used location. The MOS uses this byte to test if a *KEY definition is present when a function key has been defined. If the key pointer byte and the TOP pointer byte are the same, the MOS inserts the definition directly after the last definition (as pointed to by the pointer and TOP bytes). If, on the other hand, the pointer byte and TOP byte are different, the MOS knows that a definition is already present for the function key just defined and that it must do some reshuffling of the bytes in the buffer.
10 REM *** FUNCTION KEY PRINTER *** 20 REM *(C) Bruce Smith & Acorn User* 30 oswrch=&FFEE 40 osasci=&FFE3 50 PROCkeys1 (&C00) 60 *KEY0 CALL &C00|M 70 END 80 : 1000 DEF PROCkeys1(addr) 1001 LOCAL key, pointer 1002 key=&B00 1003 pointer=&B10 1004 FOR pass=0 TO 3 STEP3 1005 P%=addr 1006 [OPT pass 1007 LDX #0 1008 .main_loop 1009 TXA 1010 ASL A 1011 TAX 1012 JSR print_word_key 1013 LDA number_table,X 1014 JSR oswrch 1015 LDA number_table+1,X
1016 JSR oswrch
1017 TXA 1018 LSR A 1019 TAX 1020 LDA #&20 1021 JSR oswrch 1022 LDA key,X 1023 CMP pointer 1024 BNE over 1025 JMP update 1026 .over 1027 TAY 1028 INY 1029 .next_character 1030 LDA key,Y 1031 CMP #13 1032 BEQ carriage_return 1033 JSR oswrch 1034 INY 1035 BNE next_character 1036 .carriage_return 1037 LDA #ASC"|" 1038 JSR oswrch 1039 LDA #ASC"M" 1040 JSR oswrch 1041 .update 1042 LDA#13 1043 JSR osasci 1044 INX 1045 CPX#16 1046 BNEmain_loop 1047 RTS 1048 .print_word_key 1049 LDY#6 1050 .next_letter 1051 LDA spell_key,Y 1052 JSR oswrch 1053 DEY 1054 BNE next_letter 1055 RTS 1056 .number_table 1057 EQUS" 0 1 2 3 4 5 6 7 " 1058 EQUS"8 9101112131415" 1059 .spell_key 1060 EQUS " YEK* " 1061 ] 1062 NEXT 1063 ENDPROC
Program 2.1. PROCkeys1 - a simple function key lister
Converting the hard facts of Function Key Buffer operation into some suitable machine code is relatively easy and the main areas of coding are straightforward. From this we can see the main routines of the code which, in everyday terms, are a follows:
(a) Print the string '*KEY' followed by the current key number (therefore we need a key counter!).
(b) Obtain the key pointer and if the same as the TOP pointer move onto the next function key.
(c) Else increment key pointer and print the definition string until the RETURN character is found.
(d) Print |M and do a RETURN.
(e) Increment the function key counter.
(f) Repeat the whole process until all sixteen keys have been printed.
The X register is used to keep a count of the current key being investigated so initially this is set to zero (line 1007). The register is also used as an index into the key number look-up table (lines 1056 to 1058) where each of the ASCII codes {key number occupies two bytes. To ensure that the correct offset is located, the key count must be multiplied by two using an arithmetic shift left (lines 1009 to 10ll). The word '*KEY' and the current key number are printed using the subroutine calls of lines 1012 to 1016.
A definition present test is performed in line 1022 and 1023 and a branch over executed (line 1024) if one is found. A simple index, extract and print routine is used to print the definition string (lines 1036 to 1040) before the key count in the index register is updated (lines 1041 to 1047).
10 REM *FUNCTION KEY DEFINITIONS V2* 20 PROCkeys2(&A00) 30 *KEY0 CALL&A00|M 40 END 50 : 1070 DEF PROCkeys2 (addr) 1071 FOR pass=0 TO 3 STEP3 1072 P%=addr 1073 [ 1074 OPT pass 1075 .entry 1076 LDA #0 1077 STA key 1078 STA offset 1079 .mainloop 1080 JSR &FFE7 1081 JSR printwordkey 1082 LDX key 1083 LDA numbertable,X 1084 INX 1085 JSR &FFEE 1086 LDA numbertable,X 1087 JSR &FFEE 1088 INX 1089 STX key 1090 LDA #32 1091 JSR &FFEE 1092 LDX offset 1093 LDA &B00,X 1094 STA keystart 1095 INC keystart 1096 LDA &B10 1097 STA endpointer 1098 LDX #&F 1099 .keyend 1100 LDA &B00,X 1101 CMP endpointer 1102 BCS nexttry 1103 CMP keystart 1104 BCC nexttry 1105 STA endpointer 1106 .nexttry 1107 DEX 1108 BPL keyend 1109 LDA endpointer 1110 CMP keystart 1111 BCC nextkey 1112 LDX keystart 1113 .printdef 1114 LDA &B00,X 1115 CMP #128 1116 BCC asciichr 1117 PHA 1118 LDA #ASC"|" 1119 JSR &FFEE 1120 LDA #ASC"!" 1121 JSR &FFEE 1122 PLA 1123 AND #&7F 1124 .asciichr 1125 CMP #32 1126 BCS notcontrol 1127 PHA 1128 LDA #ASC"|" 1129 JSR &FFEE 1130 PLA 1131 CLC 1132 ADC #64 1133 JSR &FFEE 1134 JMP nextcharacter 1135 .notcontrol 1136 CMP #127 1137 BNE over 1138 LDA #ASC"|" 1139 JSR &FFEE 1140 LDA #ASC"?" 1141 JSR &FFEE 1142 JMP nextcharacter 1143 .over 1144 CMP#124 1145 BNE not 1146 LDA #ASC"|" 1147 JSR &FFEE 1148 JSR &FFEE 1149 JMP nextcharacter 1150 .not 1151 JSR &FFEE 1152 .nextcharacter 1153 CPX endpointer 1154 BEQ nextkey 1155 INX 1156 JMP printdef 1157 .nextkey 1158 INC offset 1159 LDA offset 1160 CMP #16 1161 BNE notfinished 1162 JSR &FFE7 1163 RTS 1164 .notfinished 1165 JMP mainloop 1166 .printwordkey 1167 LDY #6 1168 .nextletter 1169 LDA spellkey,Y 1170 JSR &FFEE 1171 DEY 1172 BNE nextletter 1173 RTS 1174 .numbertable 1175 EQUS" 0 1 2 3 4 5 6 7 " 1176 EQUS"8 9101112131415" 1177 .spellkey 1178 EQUS" YEK* " 1179 .key 1180 EQUB 0 1181 .keystart 1182 EQUB 0 1183 .endpointer 1184 EQUB 0 1185 .offset 1186 EQUB 0 1187 ] 1188 NEXT 1189 ENDPROC
Program 2.2. PROCkeys2 - the complete function key lister.
The definition printing routine (line 1113) begins by testing the definition for a character code greater than 128 (entered previously with the '|!' sequence). If one is present this sequence is printed; either way, control progresses to line 1124 where a control character (less than &32) is tested for. If a control character is found, the '|' character is printed followed by the ASCII code of the control character representation, obtained by adding &40 to it. Thus CTRL-L is printed as '|L' .
The end_pointer bytes are used to keep track of the length ofthe entered key definition as previously calculated by the key_end coding (lines 1099 to 1112), and the print_def loop continues until the entire function key definition is printed. The main_loop is executed sixteen times to print all the function key definitions. The final output of this program is shown in Figure 2.6.
*KEY 0 CALL&A00|M *KEY 1 CLS|M *KEY 2 *GREPL|M *KEY 3 LIST|M *KEY 4 *ASSFORM|M *KEY 5 *INSPECT *KEY 6 *BASFORM|M *KEY 7 FORN=&70 TO &7F:P.?N:N.|M *KEY 8 *EXMON|M *KEY 9 *BASIC|M *KEY 10 OLD|MLIST|M *KEY 11 *KEY 12 *KEY 13 *KEY 14 *KEY 15
Fig. 2.6. Typical output of Program 2.2.
Program 2.2
Procedure title : PROCkeys2
Line numbers : 1070 to 1189
Variables required : addr
Length : 246 bytes
Zero page requirements : none
Registers changed : A, X, Y
10 REM *** PROGRAM INFORMATION *** 20 himem=HIMEM 30 himem=himem-&200 40 HIMEM=himem 50 PROCinfo (&70,HIMEM) 60 *KEY0 CALL HIMEM|M 70 END 80 : 1200 DEF PROCinfo (current,addr) 1201 FOR pass=0 TO 3 STEP3 1202 P%=addr 1203 [ 1204 OPT pass 1205 LDX #title MOD 256 1206 LDY #title DIV 256 1207 JSR print_message 1208 .do_page 1209 LDX #message1 MOD 256 1210 LDY #message1 DIV 256 1211 JSR print_message 1212 LDA &18 1213 JSR hex_out 1214 LDA #0 1215 JSR hex_out 1216 JSR &FFE7 1217 .do_top 1218 LDX #message4 MOD 256 1219 LDY #message4 DIV 256 1220 JSR print_message 1221 LDA &13 1222 JSR hex_out 1223 LDA &12 1224 JSR hex_out 1225 JSR &FFE7 1226 .do_himem 1227 JSR &FFE7 1228 LDX #message2 MOD 256 1229 LDY #message2 DIV 256 1230 JSR print_message 1231 LDA &7 1232 JSR hex_out 1233 LDA &6 1234 JSR hex_out 1235 JSR &FFE7 1236 .do_lomem 1237 LDX #message3 MOD 256 1238 LDY #message3 DIV 256 1239 JSR print_message 1240 LDA &1 1241 JSR hex_out 1242 LDA &0 1243 JSR hex_out 1244 JSR &FFE7 1245 JSR &FFE7 1246 .do_size 1247 LDX #message5 MOD 256 1248 LDY #message5 DIV 256 1249 JSR print_message 1250 SEC 1251 LDA &13 1252 SBC &18 1253 JSR hex_out 1254 LDA &12 1255 JSR hex_out 1256 LDX #bytes MOD 256 1257 LDY #bytes DIV 256 1258 JSR print_message 1259 .do_next_free 1260 LDX #message6 MOD 256 1261 LDY #message6 DIV 256 1262 JSR print_message 1263 LDA &3 1264 JSR hex_out 1265 LDA &2 1266 JSR hex_out 1267 JSR &FFE7 1268 JSR &FFE7 1269 .memory_left 1270 LDX #message7 MOD 256 1271 LDY #message7 DIV 256 1272 JSR print_message 1273 SEC 1274 LDA &6 1275 SBC &2 1276 STA store 1277 LDA &7 1278 SBC &3 1279 JSR hex_out 1280 LDA store 1281 JSR hex_out 1282 LDX #bytes MOD256 1283 LDY #bytes DIV 256 1284 JSR print_message 1285 RTS 1286 .print_message 1287 STX current 1288 STY current+1 1289 LDY #0 1290 .loop 1291 LDA (current),Y 1292 BMI all_done 1293 JSR &FFE3 1294 INY 1295 BNE loop 1296 .all_done 1297 RTS 1298 .hex_out 1299 PHA 1300 LSR A 1301 LSR A 1302 LSR A 1303 LSR A 1304 SED 1305 CLC 1306 ADC #&90 1307 ADC #&40 1308 CLD 1309 JSR &FFEE 1310 PLA 1311 AND #15 1312 SED 1313 CLC 1314 ADC #&90 1315 ADC #&40 1316 CLD 1317 JMP &FFEE 1318 .title EQUB 12 1319 EQUS" Program" 1320 EQUS" Information Service" 1321 EQUD &0D0D0D0D 1322 EQUB 255 1323 .message1 1324 EQUS"PAGE : &" 1325 EQUB 255 1326 .message2 1327 EQUS"HIMEM : &" 1328 EQUB 255 1329 .message3 1330 EQUS"LOMEM : &" 1331 EQUB 255 1332 .message4 1333 EQUS"TOP : &" 1334 EQUB 255 1335 .message5 1336 EQUS"Program Size=&" 1337 EQUB 255 1338 .message6 1339 EQUS"Next Free Location=&" 1340 EQUB 255 1341 .message7 1342 EQUS"Memory Remaining=&" 1343 EQUB 255 1344 .bytes 1345 EQUS" bytes" 1346 EQUD &0D0D 1347 EQUB 255 1348 .store 1349 EQUB 0 1350 ] 1351 NEXT 1352 ENDPROC
Program 3.1. PROCinfo - provides details on system pseudo-variables.
PAGE
HIMEM
LOMEM
TOP
Program size
Next free location
Memory remaining
All the information required to calculate each of these values can be found in zero page. Figure 3.1 lists the byte allocation for the first couple of dozen locations.
The assembler is quite straightforward and is split into easy-to-handle segments. The screen information title is first printed onto the screen using the print_message subroutine (lines 1286 to 1297). The address of the string to be printed is transferred to the subroutine via the index registers. On return, the relevant data is extracted from zero page and printed in hexadecimal format using a fairly standard hex to ASCII print routine, 'hex_out' (lines 1298 and 1317).
&00 - &01 | : LOMEM |
&02 - &03 | : VARTOP (top of variables) |
&04 - &05 | : Basic Stack Pointer |
&06 - &07 | : HIMEM |
&08 - &09 | : ERL |
&0A | : Text pointer index |
&9B - &9C | : Text pointer |
&0D - &11 | : RND seed |
&12 - &13 | : TOP |
&16 - &17 | : Error vector |
&18 | : PAGE byte |
Program Information Service PAGE : &1C00 TOP : &2559 HIMEM : &7A00 LOMEM : &2559 Program Size=&0959 bytes Next Free Location=&26C7 Memory Remaining=&5339 bytes
Fig. 3.2. Typical of output produced by Program 3.2.
10 REM *** LIST ALL PROGRAM VARIABLES *** 20 PROCvars(&70,&71,&73,&A00) 30 *KEY 1 CALL&A00|M 40 END 50 : 1400 DEF PROCvars(asc,varpointer,varstr ing,addr) 1401 FOR pass=0 TO 3 STEP 3 1402 P%=addr 1403 [ OPT pass 1404 .variables 1405 LDA #12 1406 JSR &FFEE 1407 LDA #14 1408 JSR &FFEE 1409 LDA #65 1410 STA asc 1411 LDA #&82 1412 STA varpointer 1413 LDA #4 1414 STA varpointer+1 1415 .loop 1416 LDY #1 1417 LDA (varpointer),Y 1418 BEQ update 1419 STA varstring+1 1420 LDY #0 1421 LDA (varpointer),Y 1422 STA varstring 1423 .next_var 1424 LDA #13 1425 JSR &FFE3 1426 LDA asc 1427 JSR &FFE3 1428 LDY #2 1429 .print_loop 1430 LDA ( varstring),Y 1431 BEQ end_print 1432 JSR&FFE3 1433 INY 1434 JMP print_loop 1435 .end_print 1436 LDY #1 1437 LDA ( varstring),Y 1438 BEQ update 1439 TAX 1440 DEY 1441 LDA (varstring),Y 1442 STA varstring 1443 STX varstring+1 1444 JMP next_var 1445 .update 1446 LDA #2 1447 CLC 1448 ADC varpointer 1449 CMP #&F6 1450 BEQ finished 1451 STA varpointer 1452 INC asc 1453 JMP loop 1454 .finished 1455 LDA #13 1456 JSR &FFE3 1457 LDA #15 1458 JSR &FFE3 1459 RTS 1460 ] 1461 NEXT 1462 ENDPROC
Program 3.2. PROCvars - lists all program variables.
An understanding of variable storage is essential to follow the program's operation. In addition to the resident integer variables there are basically two other types of variable. One of these variables is postfixed with a % sign to signify that it is also an integer, while a variable without the % defines that it is a floating point variable. When a program is run, the BASIC interpreter extracts each variable from the program and places it in a fixed format above the main program and below TOP. The format is as follows:
(a) A two-byte address which points to the next variable starting with the same leiter. If none are present these bytes contain zero.
(b) The variable name in ASCII format excluding the first letter of the variable, e.g. START is stored as TART.
(c) A zero byte to mark the end of the variable name.
(d) The binary representation of the value assigned to that variable. This is stored in four bytes for an integer variable and five bytes for a floating point variable.
We can see from item (a) that it is quite easy to move from one variable to another, starting with the same letter, simply by extracting the address pointer from each variable 'definition' in turn. However, we need to know exactly where the first variable is located and Acorn have provided, by design, a variable pointer table on Page 4 in block zero RAM. Figure 3.3 details the locations holding the pointers for the characters A to Z and a to z. If both locations for a particular character contain zero then no variable beginning with that letter is present. 28 The BBC Micro Machine Code Portfolio
Character | LSB address | MSB address | |
A | &482 | &483 | |
B | &484 | &485 | |
C | &486 | &487 | |
D | &488 | &489 | |
E | &48A | &48B | |
F | &48C | &48D | |
G | &48E | &48F | |
H | &490 | &491 | |
I | &492 | &493 | |
J | &494 | &495 | |
K | &496 | &497 | |
L | &498 | &499 | |
M | &49A | &49B | |
N | &49C | &49D | |
O | &49E | &49F | |
P | &4A9 | &4A1 | |
Q | &4A2 | &4A3 | |
R | &4A4 | &4A5 | |
S | &4A6 | &4A7 | |
T | &4A8 | &4A9 | |
U | &4AA | &4AB | |
V | &4AC | &4AD | |
W | &4AE | &4AF | |
X | &4B9 | &4B1 | |
Y | &4B2 | &4B3 | |
Z | &4B4 | &4B5 | |
a | &4C2 | &4C3 | |
b | &4C4 | &4C5 | |
c | &4C6 | &4C7 | |
d | &4C8 | &4C9 | |
e | &4CA | &4CB | |
f | &4CC | &4CD | |
g | &4CE | &4CF | |
h | &4D0 | &4D1 | |
i | &4D2 | &4D3 | |
j | &4D4 | &4D5 | |
k | &4D6 | &4D7 | |
1 | &4D8 | &4D9 | |
m | &4DA | &4DB | |
n | &4DC | &4DD | |
o | &4DE | &4DF | |
p | &4E9 | &4E1 | |
q | &4E2 | &4E3 | |
r | &4E4 | &4E5 | |
s | &4E6 | &4E7 | |
t | &4E8 | &4E9 | |
u | &4EA | &4EB | |
v | &4EC | &4ED | |
w | &4EE | &4EF | |
x | &4F0 | &4F1 | |
y | &4F2 | &4F3 | |
z | &4F4 | &4F5 | |
asc addr end_print finished loop next_var pass print_loop update varpointer varstring variables
Fig. 3.5. Typical output produced by Program 3.3.
Program 3.2
Procedure title : PROCvars
Line numbers : 1400 to 1462
Length : 103 bytes
Zero page requirements : 5 bytes, four forming vectors
Registers changed : A, X, Y
10 REM * A Basic Formatted Listing * 20 FOR loop=0 TO 100 30 PRINT loop : NEXT loop 40 INPUT "A number" N% 50 IF N%=10 PRINT"Correct" ELSE PRINT "wrong" 60 REPEAT : INPUT "Code" C$ 70 FOR wait=0 TO 1000 : NEXT wait 80 UNTIL C$="END" >LIST 10 REM * A Basic Formatted Listing * 20 FOR loop=0 TO 100 30 PRINT loop : NEXT loop 40 INPUT "A number" N% 50 IF N%=10 PRINT"Correct" ELSE PRINT "wrong" 60 REPEAT : INPUT "Code" C$ 70 FOR wait=0 TO 1000 : NEXT wait 80 UNTIL C$="END"
Fig. 4.1. A BASIC listing with and without the BASIC formatter
The BASIC formatter splits multistatement lines by issuing a carriage return each time it encounters a colon. It also splits IF. .
.THEN. . .ELSE structures in addition to indenting them along with REPEAT . . . UNTIL and FOR. . .NEXT loops. Figure 4. 1 shows the type of listing the BASIC Formatter is capable of. Now for the programs!
LDA #0
10 REM *** LISTING FORMATTER *** 20 PROCbasic_format(&900) 30 *KEY0 CALL &900|M 40 *KEY1 CALL &928|M 50 END 60 : 1480 DEF PROCbasic_format(addr) 1481 interpreter=&E0A4 1482 FOR pass=0 TO 3 STEP3 1483 P%=addr 1484 [OPT pass 1485 .on 1486 LDX #&00 1487 .next_character 1488 LDA message,X 1489 JSR &FFE3 1490 INX 1491 CMP#13 1492 BNE next_character 1493 LDA &1F 1494 STA listo 1495 LDX #&07 1496 STX &1F 1497 LDA &20E 1498 STA address 1499 LDA &20F 1500 STA address+1 1501 LDA #format MOD 256 1502 STA &20E 1503 LDA #format DIV 256 1504 STA &20F 1505 RTS 1506 .off 1507 LDX #&00 1508 .next_character 1509 LDA message2,X 1510 JSR &FFE3 1511 INX 1512 CMP #13 1513 BNE next_character 1514 LDA address 1515 STA &20E 1516 LDA address+1 1517 STA &20F 1518 LDA listo 1519 STA &1F 1520 RTS 1521 .format 1522 PHA 1523 CMP #ASC(":") 1524 BNE no_colon 1525 JSR output 1526 LDA #&00 1527 STA byte 1528 STA byte+1 1529 BEQ not_else 1530 .no_colon 1531 LDA #&01 1532 CMP &1E 1533 BNE not_same 1534 LDA #&00 1535 STA byte+2 1536 STA byte+3 1537 STA byte+4 1538 .not_same 1539 CPY #&00 1540 BEQ carry_on 1541 .not_else 1542 PLA 1543 JMP interpreter 1544 .carry_on 1545 LDA &37 1546 CMP #&E7 1547 BNE not_if 1548 INC byte+2 1549 .not_if 1550 CMP #&8B 1551 BNE not_else 1552 INC byte+3 1553 JSR output 1554 JSR interpreter 1555 JMP not_else 1556 .output 1557 LDA #&0A 1558 JSR interpreter 1559 LDA #&0D 1560 JSR interpreter 1561 CLC 1562 LDA &3B 1563 ADC &3C 1564 ADC byte+2 1565 TAX 1566 INX 1567 LDA #&20 1568 JSR interpreter 1569 JSR interpreter 1570 JSR interpreter 1571 .more_spaces 1572 JSR interpreter 1573 JSR interpreter 1574 DEX 1575 BNE more_spaces 1576 RTS 1577 .message 1578 EQUS" Formatter on!" 1579 EQUB 7 1580 EQUB 13 1581 .message2 1582 EQUS " Formatter off!" 1583 EQUB 7 1584 EQUB 13 1585 .byte 1586 EQUS" " 1587 .address 1588 EQUS" " 1589 .listo EQUB 0 1590 ] 1591 NEXT pass 1592 ENDPROC
Program 4.1. PROCbasic_format - neatly formats a BASIC listing.
On entry into 'format' ,through the reset WRCHV the accumulator contains the character to be written. This is tested to see if it is a colon. If this test fails a branch to 'no_colon' is performed. Assuming a colon is present, the 'output' routine at line 1556 is called to perform a line feed and carriage return and a series of spaces printed. The output routine uses a direct jump into the MOS to do the printing. This is necessary as we have intercepted the normal WRCHV address.
Incidently, disassembling from this address, &E9A4, provides an interesting insight into how the Beeb programs the CRTC to display characters. As a machine code programmer you must be in possession of a suitable disassembler, so have a look! But I digress, so back to the program description. On return from the output call (line 1526) the 'byte' locations, which act as counters, are cleared and a forced branch to 'not_else' is performed, which prints the character to the screen (line 1541).
Routing around the rest of the code takes place if any of the indenting loop commands already mentioned are identified by intercepting the count value held at &1E and used by LISTO. Special treatment of the IF statement is required to ensure that any subsequent ELSE is generated both on a new line and further indented - this is because ELSE is normally ignored by LISTO. These two commands are identified by their token values before the 'interpreter' call hands them over to the BASIC detokenising routine for expansion. The codes and the entry points are as follows:
IF | (= &E7) entry at line 1546 |
ELSE | (= &8B) entry at line 1550 |
10 REM *** ASSEMBLER FORMATTER *** 20 PROCass_format (&C00) 30 *KEY0 CALL &C00|M 40 *KEY1 CALL &C29|M 50 END 60 : 1600 DEF PROCass_format (addr) 1601 oswrch=?&20E+(?&20F*256) 1602 FOR pass=0 TO 3 STEP 3 1603 P%=addr 1604 [ 1605 OPT pass 1606 .on 1607 LDX #0 1608 .nextchr 1609 LDA message,X 1610 JSR &FFE3 1611 INX 1612 CMP #13 1613 BNE nextchr 1614 LDA#0 1615 STA byte +1 1616 LDA &20E 1617 STA byte+2 1618 LDA &20F 1619 STA byte+3 1620 LDA #assembler MOD 256 1621 STA &20E 1622 LDA #assembler DIV 256 1623 STA &20F 1624 RTS 1625 .off 1626 LDX #0 1627 .nextchr 1628 LDA message2,X 1629 JSR &FFE3 1630 INX 1631 CMP #13 1632 BNE nextchr 1633 LDA byte+2 1634 STA &20E 1635 LDA byte+3 1636 STA &20F 1637 RTS 1638 .assembler 1639 STA byte 1640 PHP 1641 TXA 1642 PHA 1643 LDA byte+1 1644 BNE testshut 1645 LDA byte 1646 CMP #ASC("[") 1647 BNE return 1648 STA byte+1 1649 .return 1650 LDA byte 1651 JSR oswrch 1652 PLA 1653 TAX 1654 PLP 1655 LDA byte 1656 RTS 1657 .testshut 1658 LDA byte 1659 CMP #93 1660 BNE testcr 1661 LDA #0 1662 STA byte+1 1663 BEQ return 1664 .testcr 1665 CMP #13 1666 BNE testlabel 1667 LDA #0 1668 STA byte+4 1669 BEQ return 1670 .testlabel 1671 CMP #ASC(".") 1672 BEQ signal 1673 CMP #ASC(":") 1674 BCC return 1675 LDA byte+4 1676 BNE return 1677 LDX #10 1678 LDA #32 1679 .spaces 1680 JSR oswrch 1681 DEX 1682 BNE spaces 1683 LDA #1 1684 .signal 1685 STA byte+4 1686 JMP return 1687 .message 1688 EQUS"Assembler " 1689 EQUS"Formatter On!" 1690 EQUW &0D07 1691 .message2 1692 EQUS"Assembler " 1693 EQUS"Formatter Off!" 1694 EQUW &0D07 1695 .byte 1696 EQUS" " 1697 ] 1698 NEXT 1699 ENDPROC
Program 4.3. PROCass_format - makes an assembler listing more readable.
Like its BASIC predecessor, the Assembler Formatter has two entry points to turn the utility on and off. The 'on' entry point is at line 1606 which outputs the 'Assembler Formatter On' message before saving and redirecting the contents of the WRCHV to the 'assembler' entry point at line 1638. The 'off' routine, entered at line 1625 performs the reverse operation.
When the formatter is on, all output produced by the Beeb is channelled through the 'assembler' routine via WRCHV. After preserving the program status (lines 1639 to 1642) the accumulator's contents are tested to see if they contain the '[' code to indicate the start of assembler (line 1646). If this test succeeds, the code is stored at 'byte+1' .As you may have noticed, the code immediately before this tested this particular location to see if it were non-zero which would denote an already open assembler listing. This test routine would therefore be jumped over to the test_shut routine (line 1657). This section of code first tests to see if the close bracket, end of assembler mark, has been found in which case the 'byte' values are reset and the normal oswrch output pursued.
If a carriage return is not present (lines 1664 to 1669) the 'test_label' routine is invoked. If the label start character, a full-stop, is present the fact is signalled in 'byte-4' and the routine completed; a delimiting colon is treated in a similar manner. If neither of these characters is encountered, the X register is loaded with the number of padding spaces to be printed (line 1677). I chose to use ten though you can adjust this to your own taste. The 'spaces' loop is entered and exited on completion of printing the ten spaces. Mnemonics will subsequently be printed from this ten spaces in position, while labels are printed as usual.
Program 4.2
Procedure title : PROCass_format
Variables required : addr
Line numbers : 1600 to 1699
Length : 214 bytes
Zero page requirements : none
XY+0 to XY+1 | : | Filename address. Filename must be terminated by RETURN. |
XY+2 to XY+5 | : | File load address, stored low byte first. |
XY+6 to XY+9 | : | Run address of file, stored low byte first. |
XY+10 to XY+13 | : | Data start address to be saved. |
XY+14 to XY+17 | : | Data end address. |
0 | : Save block of memory as detailed in parameter block. |
1 | : Write information in parameter block to catalogue entry. |
2 | : Write load address only for existing file. |
3 | : Write the run address only for an existing file. |
4 | : Write file attributes only for an existing file. |
5 | : Read a file's catalogue information to parameter block. |
6 | : Delete file named in parameter block. |
255 | : Load the file detailed in the parameter block. |
10 REM *** SAVE SCREEN MEMORY *** 20 PROCsavescreen (&C00) 30 inc=5 40 X=640 : Y=512 50 MODE 4 60 FOR loop=1 TO 50 70 MOVE X,Y 80 DRAW X+inc,Y 90 DRAW X+inc,Y+inc 100 DRAW X,Y+inc 110 DRAW X,Y 120 X=X-20 : Y=Y-20 130 inc=inc+40 140 NEXT 150 CALL &C00 160 END 170 : 1700 DEF PROCsavescreen (addr) 1701 FOR pass=0 TO 3 STEP 3 1702 P%=addr 1703 [ 1704 OPT pass 1705 .save_screen 1706 LDA #135 1707 JSR &FFF4 1708 TYA 1709 BEQ dump1 1710 CMP#3 1711 BCC dump1 1712 CMP#4 1713 BEQ dump2 1714 CMP#5 1715 BEQ dump2 1716 CMP #7 1717 BEQ teletext 1718 .error 1719 LDY #0 1720 .loop 1721 LDA message,Y 1722 BEQ finished 1723 JSR &FFE3 1724 INY 1725 BNE loop 1726 .finished 1727 RTS 1728 .dump1 1729 LDA #&30 1730 STA paramblk+3 1731 STA paramblk+7 1732 STA paramblk+&0B 1733 LDA #0 1734 JMP osfile 1735 .dump2 1736 LDA #&58 1737 STA paramblk+3 1738 STA paramblk+7 1739 STA paramblk+&0B 1740 LDA#0 1741 JMP osfile 1742 .teletext 1743 LDA #&7C 1744 STA paramblk+3 1745 STA paramblk+7 1746 STA paramblk+&0B 1747 LDA #0 1748 JMP osfile 1749 .osfile 1750 LDX #paramblk MOD 256 1751 LDY #paramblk DIV 256 1752 JMP &FFDD 1753 .filename 1754 EQUS"SSAVED" 1755 EQUB 13 1756 .paramblk 1757 EQUB filename MOD 256 1758 EQUB filename DIV 256 1759 EQUD&3000 1760 EQUD 0 1761 EQUD&3000 1762 EQUD&7FFF 1763 .message 1764 EQUB 7 1765 EQUS"Not a graphics Mode" 1766 EQUB 13 1767 EQUB 0 1768 ] 1769 NEXT 1770 ENDPROC
Program 5.1. PROCsavescreen - saves the screen memory to tape or disk
The program acts 'intelligently' in this respect by obtaining the current screen mode from the Y register after an *FX135 call (lines 1706 to 1708). If, after the comparison of line 1710, the carry is clear a MODE of less than 3 is indicated and the branch to 'dumpl' performed. If a mode value of 4 or 5 is determined, 'dump2' is sought while a branch to 'teletext' is executed if 7 is returned. Note that the 'error' loop is entered if the screen is in MODE 3 or MODE 6; this prints out the 'Not a graphics Mode' message from line 1765 and the routine is exited.
Each of these sections of code simply seed the first page number of the current graphics MODE into the correct places within the parameter block. If the graphics MODE was MODE 1 then the branch to 'dump1' would seed the value &30 into the three bytes at paramblk+3, paramblk+7 and paramblk+&0B, prior to loading the accumulator with 0 and jumping to 'osfile' at line 1749. Here the address of 'paramblk' is loaded into the index registers and a JMP to OSFILE at &FFDD performed.
The parameter block is located at the top of the calling machine code, lines 1756 to 1762 and the EQU functions used to prime the static contents. The filename is stored at 'filename' (line 1753) and I have chosen to use SSAVED, but this can be changed to suit your own needs, of course.
The BASIC test routine simply draws a succession of squares in MODE 4 before using the machine code to save the screen's contents.
The following program, Program 5.2, can be used to reload screen memory.
10 REM *** LOAD SCREEN MEMORY *** 20 PROCloadscreen (&C00) 30 MODE4 40 CALL &C00 50 END 60 : 1800 DEF PROCloadscreen (addr) 1801 FOR pass=0 TO 3 STEP 3 1802 P%=addr 1803 [ 1804 OPT pass 1805 .load_screen 1806 LDA #135 1807 JSR &FFF4 1808 TYA 1809 BEQ dump1 1810 CMP#3 1811 BCC dump1 1812 CMP#4 1813 BEQ dump2 1814 CMP#5 1815 BEQ dump2 1816 CMP #7 1817 BEQ teletext 1818 .error 1819 LDY #0 1820 .loop 1821 LDA message,Y 1822 BEQ finished 1823 JSR &FFE3 1824 INY 1825 BNE loop 1826 .finished 1827 RTS 1828 .dump1 1829 LDA #&30 1830 STA paramblk+3 1831 STA paramblk+7 1832 STA paramblk+&0B 1833 LDA #255 1834 JMP osfile 1835 .dump2 1836 LDA #&58 1837 STA paramblk+3 1838 STA paramblk+7 1839 STA paramblk+&0B 1840 LDA #255 1841 JMP osfile 1842 .teletext 1843 LDA #&7C 1844 STA paramblk+3 1845 STA paramblk+7 1846 STA paramblk+&0B 1847 LDA #255 1848 JMP osfile 1849 .osfile 1850 LDX #paramblk MOD 256 1851 LDY #paramblk DIV 256 1852 JMP &FFDD 1853 .filename 1854 EQUS"SSAVED" 1855 EQUB 13 1856 .paramblk 1857 EQUB filename MOD 256 1858 EQUB filename DIV 256 1859 EQUD&3000 1860 EQUD 0 1861 EQUD&3000 1862 EQUD&7FFF 1863 .message 1864 EQUB 7 1865 EQUS"Not a graphics Mode" 1866 EQUB 13 1867 EQUB 0 1868 ] 1869 NEXT 1870 ENDPROC
Program 5.2. PROCloadscreen - loads a saved graphics screen back into
screen memory.
10 REM *** PRINTER SCREEN DUMPER *** 20 REM *** EPSON FX and STAR *** 30 MODE 5 40 X=640 : Y=512 50 increment=5 60 FOR loop=1 TO 50 70 MOVE X,Y 80 DRAW X+increment,Y 90 DRAW X+increment,Y+increment 100 DRAW X,Y+increment 110 DRAW X,Y 120 X=X-20 : Y=Y-20 130 increment=increment+40 140 NEXT 150 PROCscreen_dump(&70,&71,&72,&73,&7 5,&76,&2E00) 160 CALL screen_dump 170 END 180 : 1900 DEFPROCscreen_dump(xlo,xhi,ylo,yhi ,byte,bits,addr) 1901 FOR pass=0 TO 2 STEP 2 1902 P%=addr 1903 [ OPT pass 1904 .screen_dump 1905 LDA #2 1906 JSR &FFEE 1907 LDA #1 1908 JSR &FFEE 1909 LDA #27 1910 JSR &FFEE 1911 LDA #1 1912 JSR &FFEE 1913 LDA #65 1914 JSR &FFEE 1915 LDA #1 1916 JSR &FFEE 1917 LDA #8 1918 JSR &FFEE 1919 LDA #1 1920 JSR &FFEE 1921 LDA #10 1922 JSR &FFEE 1923 LDA# &FF 1924 STA ylo 1925 LDA# &3 1926 STA yhi 1927 .next_row 1928 LDA #&0 1929 STA xlo 1930 LDA# &0 1931 STA xhi 1932 JSR duel_density 1933 LDA# &1 1934 JSR &FFEE 1935 LDA# &D 1936 JSR &FFEE 1937 SEC 1938 LDA ylo 1939 SBC# 32 1940 STA ylo 1941 BCS check_finish 1942 DEC yhi 1943 .check_finish 1944 LDA yhi 1945 CMP# &FF 1946 BNE next_row 1947 LDA ylo 1948 CMP# &FF 1949 BNE next_row 1950 LDA #1 1951 JSR &FFEE 1952 LDA #12 1953 JSR &FFEE 1954 LDA #1 1955 JSR &FFEE 1956 LDA #27 1957 JSR &FFEE 1958 LDA #1 1959 JSR &FFEE 1960 LDA #64 1961 JSR &FFEE 1962 LDA #3 1963 JSR &FFEE 1964 RTS 1965 .duel_density 1966 LDA# &1 1967 JSR &FFEE 1968 LDA #27 1969 JSR &FFEE 1970 LDA #1 1971 JSR &FFEE 1972 LDA #76 1973 JSR &FFEE 1974 LDA #1 1975 JSR &FFEE 1977 JSR &FFEE 1978 LDA #1 1979 JSR &FFEE 1980 LDA #2 1981 JSR &FFEE 1982 .next_byte 1983 LDA #0 1984 STA bits 1985 LDA #128 1986 STA byte 1987 .read_pixel 1988 LDA #9 1989 LDX #xlo 1990 LDY #0 1991 JSR &FFF1 1992 LDA xlo+4 1993 AND #&FF 1994 BEQ step4 1995 LDA byte 1996 ORA bits 1997 STA bits 1998 .step4 1999 SEC 2000 LDA ylo 2001 SBC #4 2002 STA ylo 2003 BCS rotate 2004 DEC yhi 2005 .rotate 2006 CLC 2007 ROR byte 2008 BCC read_pixel 2009 .print_pattern 2010 LDA #1 2011 JSR &FFEE 2012 LDA bits 2013 JSR &FFEE 2014 CLC 2015 LDA ylo 2016 ADC #32 2017 STA ylo 2018 BCC over 2019 INC yhi 2020 .over 2021 CLC 2022 LDA xlo 2023 ADC #2 2024 STA xlo 2025 BCC leap_frog 2026 INC xhi 2027 .leap_frog 2028 LDA xhi 2029 CMP #5 2030 BNE do_again 2031 RTS 2032 .do_again 2033 JMP next_byte 2034 ] 2035 NEXT pass 2036 ENDPROC
Program 5.3. PROCscreen_dump - outputs the graphics screen to a connected printer.
The machine code of the program assembles just below the memory required by either of the 20K screen modes. It would be a good idea to obtain a second source coding that will sit just below the MODE 4 and 5 memory, thus making the 'unused' screen memory available for use by the program. A suitable value for 'addr' in this instance would be &5600.
The major part of any graphics-printer dump program is spent preparing the pixel - in other words, converting it from its screen form into a form that the printer can handle and translate into selecting which of its eight dot-matrix pins it fires. (Yes, I know there are nine but we only use eight!) The steps required to perform this conversion process are summarised below:
(a) Read a pixel off the screen.
(b) Adjust the byte using suitable rotates.
(c) Check a counter to see if byte is complete.
(d) Adjust the value of Y and X as needed to allow for resolution changes.
(e) Send the byte to the printer in the form of a VDU1 command.
Looking at the assembler program shows that the first section of code from line 1904 to 1926 is responsible for issuing a series of VDU1 codes to the printer using OSWRCH. In BASIC terms the following is performed:
VDU 2, 1, 27, 1, 65, 1, 8, 1, 10
The VDU 2 is used to enable the printer while the intermediate codes set the line spacing to 8/72 inches. The final VDU 10 performs a line feed. Much of the code comprises these VDU 1 codes and they could be more efficiently incorporated into a look-up table if required. I have persevered with the long-winded method mainly for reasons of clarity. Lines 1922 to 1932 initialise the variables ylo, yhi and xlo, xhi. The pair ylo,yhi are loaded with &3FF which in decimal is 1023 and shows itself to be the maximum on-screen value of the Y axis. The xlo,xhi combination are set to zero. The 'dual_density' subroutine is responsible for putting the printer in graphics geaf and performs a BASIC VDU 1, 27, 1, 76, 1, 128, 2 selecting 640 dots per line in bit image mode.
Before printing, the current screen pixel details must be read from the screen. This is readily performed with OSWORD and the accumulator holding 9 (lines 1987 to 1992). The parameter block requires five bytes set out as follows using the declared variables:
xlo | - low byte X coordinate | |
xhi | - high byte X coordinate | |
ylo | - low byte Y coordinate | |
yhi | - high byte Y coordinate |
xlo+4 | - result after OSWORD call |
Fig. 5.3. Screen dump produced by Program 5.3.
Program 5.2
Procedure title : PROCloadscreen
Variables required : addr
Line numbers : 1800 to 1870
Zero page requirements : none
Registers changed : A,X,Y
Program 5.3
Procedure title : PROCscreen_dump
Variables required : xlo, xhi, ylo, yhi, byte, bits, addr
Line numbers : 1900 to 2036
Program length : 260 bytes
Zero page requirements : 7 bytes
Registers changed : A,X,Y
*** SOFT CHR CHARACTER DEFINITIONS *** 224: 32,165, 12,169,224,133,114,169, 225: 12,133,113,169, 0,133,112,133, 226: 115,208, 12,165,116,208, 12,165, 227: 115,208, 12,165,116,208, 8, 32, 228: 227, 12,208,240, 76,239, 12,165, 229: 114, 32, 89, 12,169, 58, 32,238, 230: 255,169, 32, 32,238,255,160, 0, 231: 117,112, 32, 89, 12,169, 44, 32, 232: 238,255,200,192, 8,208,241, 32, 233: 227, 12,169, 0,133,115,133,116, 234: 168,169, 13, 32,227,255, 76, 20, 235: 12,162, 0,134,117,201,100,144, 236: 8,233,100,232,134,117, 76, 93, 237: 12, 32,129, 12,162, 0,201, 10, 238: 144, 6,233, 10,232, 76,110, 12, 239: 32,129, 12, 24,105, 48, 76,238, 240: 255, 72,138,105, 48,201, 48,208, 241: 6,166,117,208, 2,169, 32, 32, 242: 238,255,104, 96,160, 7,177,112, 243: 24,101,115,133,115,144, 2,230, 244: 116,136, 16,242, 96,162, 0,189, 245: 180, 12, 48, 7, 32,238,255,232, 246: 76,167, 12, 96, 10, 13, 10, 13, 247: 42, 42, 42, 32, 83, 79, 70, 84, 248: 32, 67, 72, 82, 32, 67, 72, 65, 249: 82, 65, 67, 84, 69, 82, 32, 68, 250: 69, 70, 73, 78, 73, 84, 73, 79, 251: 78, 83, 32, 42, 42, 42, 10, 13, 252: 10, 13,255, 24,165,112,105, 8, 253: 133,112,230,114,240, 1, 96, 32, 254: 231,255,104,104, 96, 0, 0, 0,
Fig. 6.1. A typical output produced by PROCvduchrs.
Primarily these definable characters, 224 to 255, are used to create new characters whether they be fancy stylised alphanumeric characters or, more commonly, games characters. Program 6.1 provides a routine that will display the full definitions of any of these characters that have been defined. Figure 6.1 shows the output produced by the program.
User-definable characters are stored in the soft character definition area on page &C between &C00 to &CFF. Machine code programmers will know this area better as an assembly area for their code! As mentioned, eight bytes are associated with each character; thus, character VDU224 is allocated the eight bytes &C90 to &C07 inclusive; VDU 255 the bytes &C08 to &C0F, and on up to VDU 255 which is allocated the bytes &CF8 to &CFF. The first byte in each definition (the top-most one) is placed in the first byte of the corresponding memory location and so on - as Figure 6.2 illustrates.
Fig. 6.2. The byte definition storage of user-defined character 224.
As Figure 6.1 showed, the program does not print out the contents of every character - merely the characters that are or seem to be defined. This is quite simple to determine. On a power-up or reset, the MOS clears this area of memory with zero, so all the program needs to do is to add up the bytes corresponding to each VDU character. If the result is zero, no definition is present and the next character is sought. If, on the other hand, the result is non-zero then a definition is assumed and the contents printed. I say 'assumed' because it might not be a proper definition - it may, of course, be machine code! Also, the last 5 characters in the buffer, VDU 250 to VDU 255, seem to be susceptible to having garbage placed into them by the MOS.
Figure 6.3 flowcharts the program's operation. The definition test just discussed is performed by the 'test_for_definition' routine (lines 2135 to 2147 in Program 6.1). The restult of the summing is placed in Softly, Softly55 the 'addition' then a branch to 'print_definition' is executed (lines 2067 to 2070).
Fig. 6.3. The PROCvduchr flowchart.
The 'print_definition' loop (lines 2074 to 2097 in Program 6.1) begins by printing the VDU number of the current character followed by a colon. Each byte is then extracted in turn and printed to the screen in decimal form followed by a comma. After the last definition byte is printed a new line is printed and the next VDU character is sought. The 'update routine' (lines 2166 to 2173), as its name implies, increments all program counters and determines when every VDU character has been processed.
10 REM * SOFT CHR VDU'S VERSION V2 * 20 REM *(c) Bruce Smith/Acorn User * 30 PROCvdhchr(&70,&72,&73,&75,&4000) 40 *KEY0 CALL &4000|M 50 END 60 : 2050 DEF PROCvdhchr(soft_base,vdu_chara cter,addition_bytes,flag,addr) 2051 FOR pass=0 TO 3 STEP3 2052 P%=&4000 2053 [ OPT pass 2054 .start 2055 JSR set_up_screen 2056 LDA #224 2057 STA vdu_character 2058 LDA #&C 2059 STA soft_base+1 2060 LDA #0 2061 STA soft_base 2062 STA addition_bytes 2063 STA addition_bytes+1 2064 TAY 2065 .main_loop 2066 JSR test_for_definition 2067 LDA addition_bytes 2068 BNE print_definition 2069 LDA addition_bytes+1 2070 BNE print_definition 2071 JSR update 2072 BNE main_loop 2073 JMP exit 2074 .print_definition 2075 LDA vdu_character 2076 JSR binary_decimal_print 2077 LDA #ASC":" 2078 JSR &FFEE 2079 LDA #ASC" " 2080 JSR &FFEE 2081 LDY #0 2082 .loop 2083 LDA (&70),Y 2084 JSR binary_decimal_print 2085 LDA #ASC"," 2086 JSR &FFEE 2087 INY 2088 CPY #8 2089 BNE loop 2090 JSR update 2091 LDA#0 2092 STA addition_bytes 2093 STA addition_bytes+1 2094 TAY 2095 LDA #13 2096 JSR &FFE3 2097 JMP main_loop 2098 .binary_decimal_print 2099 LDX #0 2100 STX flag 2101 .hundreds 2102 CMP#100 2103 BCC no_hundreds 2104 SBC #100 2105 INX 2106 STX flag 2107 JMP hundreds 2108 .no_hundreds 2109 JSR print_decimal 2110 LDX #0 2111 .tens 2112 CMP #10 2113 BCC no_tens 2114 SBC #10 2115 INX 2116 JMP tens 2117 .no_tens 2118 JSR print_decimal 2119 CLC 2120 ADC #ASC"0" 2121 JMP &FFEE 2122 .print_decimal 2123 PHA 2124 TXA 2125 ADC #ASC"0" 2126 CMP #ASC"0" 2127 BNE no_zero 2128 LDX flag 2129 BNE no_zero 2130 LDA #32 2131 .no_zero 2132 JSR &FFEE 2133 PLA 2134 RTS 2135 .test_for_definition 2136 LDY#7 2137 .check_loop 2138 LDA (&70),Y 2139 CLC 2140 ADC addition_bytes 2141 STA addition_bytes 2142 BCC no_carry 2143 INC addition_bytes+1 2144 .no_carry 2145 DEY 2146 BPL check_loop 2147 RTS 2148 .set_up_screen 2149 LDX #0 2150 .next_character 2151 LDA table,X 2152 BMI done 2153 JSR &FFEE 2154 INX 2155 JMP next_character 2156 .done 2157 RTS 2158 .table 2159 EQUB 22 2160 EQUB 6 2161 EQUD &0D0A0D0A 2162 EQUS"*** SOFT CHR" 2163 EQUS" CHARACTER " 2164 EQUS"DEFINITIONS ***" 2165 EQUD &0D0A0D0A 2166 EQUB 255 2167 .update 2168 CLC 2169 LDA soft_base 2170 ADC#8 2171 STA soft_base 2172 INC vdu_character 2173 BEQ exit 2174 RTS 2175 .exit 2176 JSR &FFE7 2177 PLA 2178 PLA 2179 RTS 2180 ] 2181 NEXT pass 2182 ENDPROC
Program 6.1. PROCvduchrs - lists the soft character definitions.
The program incorporates a useful decimal printing routine between lines 2098 and 2134. This itself would be useful to have as a separate procedure. Character base conversion can seem difficult, but like most things in life it is quite simple to do when you know how! As it stands, the routine will convert an eight-bit binary number held in the accumulator into a three-digit decimal ASCII number, or more correctly a string of three ASCII characters. Thus, if the accumulator held 1111000l (&F1) the ASCII string "241" would be printed.
To perform this, it is first necessary to calculate how many hundreds, tens and units there are in the byte. All that is required to do this is to subtract 100 or 10 from the byte and increment a hundreds or tens count each time the subtraction leaves a remainder. Using the byte &E1 mentioned above this would work as follows. First, the hundreds:
241 | ||
-100 | ||
____ | ||
141 | hundreds count=1 | |
141 | ||
-100 | ||
____ | ||
41 | hundreds count=2 | |
41 | ||
-100 | ||
____ | ||
-59 | This result is negative |
-10 | ||
____ | ||
31 | tens count = 1 | |
31 | ||
-10 | ||
____ | ||
21 | tens count = 2 | |
21 | ||
-10 | ||
____ | ||
11 | tens count = 3 | |
11 | ||
-10 | ||
____ | ||
1 | tens count = 4 |
Line 2195: Clear occurrence counter.
Lines 2196 to 2198: Print 'variable' prompt.
Lines 2199 to 2202: Input variable name to be replaced into buffer, pointed to by the Index registers and save the strings length in 'olen'.
Lines 2203 to 2205: Print 'Replace with' prompt.
Lines 2206 to 2208: Input new variable name and store it in buffer pointed to by the Index registers.
Lines 2210 to 2213: Calculate difference in variable name lengths and save result.
Lines 2214 to 2217: Read current setting of OSHWM.
Lines 2222 to 2221: Clear registers and get first byte from program.
Lines 2222 to 2227: If byte is ASCII return, check for the TOP marker &FF.
Lines 2228 to 2230: If TOP found perform OSNEWL and exit via
'report'.
Fig. 7.1. Flowchart for PROCgrepl.
10 REM ***GLOBAL REPLACE - GREPL*** 20 himem=HIMEM 30 himem=himem-&300 40 HIMEM=himem 50 PROCgrepl (&70,&72,&74,&76,&77,&78 ,himem) 60 *KEY0 CALL HIMEM|M 70 END 80 : 2190 DEF PROCgrepl (current,last,link,o len,nlen,result,himem) 2191 FOR pass=0 TO 3 STEP 3 2192 P%=HIMEM 2193 [OPT pass 2194 .Global_replace 2195 LDA #0 :STA number 2196 LDX #old_prompt MOD 256 2197 LDY #old_prompt DIV 256 2198 JSR print_string 2199 LDX #old_name_store MOD 256 2200 LDY #old_name_store DIV 256 2201 JSR input_string 2202 STA olen 2203 LDX #new_prompt MOD 256 2204 LDY #new_prompt DIV 256 2205 JSR print_string 2206 LDX #new_name_store MOD 256 2207 LDY #new_name_store DIV 256 2208 JSR input_string 2209 .do_again 2210 SEC 2211 STA nlen 2212 SBC olen 2213 STA result 2214 LDA #&83 2215 JSR &FFF4 2216 STX current 2217 STY current+1 2218 .main_loop 2219 LDX #0 2220 TXA :TAY 2221 LDA (current),Y 2222 CMP #13 2223 BNE not_return 2224 INY 2225 LDA (current),Y 2226 CMP #&FF 2227 BNE over 2228 JSR &FFE7 2229 LDA number 2230 JMP report 2231 RTS 2232 .over 2233 CLC 2234 LDA current 2235 ADC #3 2236 STA link 2237 LDA current+1 2238 ADC #0 2239 STA link+1 2240 LDY #4 2241 BNE update4 2242 .not_return 2243 CMP #&22 2244 BNE validity_test 2245 .end_quotes 2246 INY 2247 LDA (current),Y 2248 CMP #&22 2249 BEQ update3 2250 CMP #13 2251 BNE end_quotes 2252 BEQ update4 2253 .validity_test 2254 CMP #ASC"&" 2255 BEQ hexadecimal 2256 JSR check_variable 2257 BCC match_names 2258 .hexadecimal 2259 INY 2260 LDA (current),Y 2261 JMP validity_test 2262 2263 .match_names 2264 CPY olen 2265 BNE update2 2266 DEY 2267 .next_chr 2268 LDA (current),Y 2269 CMP old_name_store,Y 2270 BNE move_on 2271 DEY 2272 BPL next_chr 2273 BMI insert_new 2274 .move_on 2275 LDY olen 2276 .update2 2277 TYA 2278 BNE update4 2279 .update3 2280 INY 2281 .update4 2282 JSR memory_update 2283 DEY 2284 BNE update4 2285 BEQ main_loop 2286 .insert_new 2287 INC number 2288 LDA current 2289 STA last 2290 LDA current+1 2291 STA last+1 2292 LDY #0 2293 CLC 2294 LDA result 2295 ADC (link),Y 2296 CMP #238 2297 BCC leap_frog 2298 JMP bad_string 2299 .leap_frog 2300 LDX #2 2301 STA (link),Y 2302 LDA result 2303 BEQ overwrite 2304 BMI shuffle_down 2305 .back 2306 JMP memory_update 2307 LDA (last),Y 2308 CMP #&FF 2309 BNE back 2310 LDX #0 2311 LDY result 2312 .shuffle_up 2313 LDA (last,X) 2314 STA (last),Y 2315 LDA last 2316 BNE low_last 2317 DEC last+1 2318 .low_last 2319 DEC last 2320 LDA last 2321 CMP current 2322 BNE shuffle_up 2323 LDA last+1 2324 CMP current+1 2325 BNE shuffle_up 2326 .overwrite 2327 LDY #0 2328 .inset_loop 2329 LDA new_name_store,Y 2330 STA (current),Y 2331 INY :CPY nlen 2332 BNE inset_loop 2333 LDX #0 2334 BEQ update2 2335 .shuffle_down 2336 LDA result 2337 EOR #&FF 2338 TAY 2339 INY 2340 .next_down 2341 LDA (last),Y 2342 STA (last-2,X) 2343 JSR memory_update 2344 CMP #&FF 2345 BNE next_down 2346 BEQ overwrite 2347 .memory_update 2348 INC current,X 2349 BNE skip_high 2350 INC current+1,X 2351 .skip_high 2352 RTS 2353 .check_variable 2354 CMP #ASC"z"+1 2355 BCS less_than 2356 CMP #ASC"_" 2357 BCS greater_than 2358 CMP #ASC"Z"+1 2359 BCS less_than 2360 CMP #ASC"A" 2361 BCS greater_than 2362 CMP #ASC"$" 2363 BEQ greater_than 2364 CPY #0 2365 BEQ less_than 2366 CMP #ASC"9"+1 2367 BCS less_than 2368 CMP #ASC"0" 2369 BCS greater_than 2370 CMP #ASC"%" 2371 BEQ greater_than 2372 .less_than 2373 CLC 2374 .greater_than 2375 RTS 2376 .print_string 2377 STX current 2378 STY current+1 2379 LDY #0 2380 .print_string2 2381 LDA (current),Y 2382 BMI no_more 2383 JSR &FFE3 2384 INY 2385 BNE print_string2 2386 .no_more 2387 RTS 2388 .input_string 2389 STX last 2390 STY last+1 2391 .input_loop2 2392 LDY #0 2393 .get_character 2394 JSR &FFE0 2395 CMP #&1B 2396 BEQ escape 2397 CMP #13 2398 BEQ string_end 2399 CMP #&7F 2400 BEQ rub_out 2401 JSR check_variable 2402 BCC get_character 2403 STA (last),Y 2404 JSR &FFE3 2405 .input_loop3 2406 INY 2407 CPY #21 2408 BEQ too_big 2409 BNE get_character 2410 .string_end 2411 TYA 2412 BEQ get_character 2413 JSR &FFE7 2414 TYA 2415 RTS 2416 .rub_out 2417 DEY 2418 BMI input_loop3 2419 JSR &FFE3 2420 JMP get_character 2421 .too_big 2422 LDX #error1 MOD 256 2423 LDY #error1 DIV 256 2424 JSR print_string 2425 PLA :PLA :RTS 2426 .escape 2427 LDA #&7E 2428 JSR &FFF4 2429 PLA :PLA :RTS 2430 .report 2431 LDX #0 2432 SEC 2433 .decimal_loop 2434 SBC #10 2435 BMI no_jump 2436 INX 2437 JMP decimal_loop 2438 .no_jump 2439 DEX 2440 CLC 2441 ADC #58 2442 PHA 2443 TXA 2444 ADC #48 2445 CMP #ASC"0" 2446 BEQ no_print 2447 JSR &FFEE 2448 .no_print 2449 PLA 2450 JSR &FFEE 2451 LDX #done MOD 256 2452 LDY #done DIV 256 2453 JMP print_string 2454 .bad_string 2455 LDX #error2 MOD 256 2456 LDY #error2 DIV 256 2457 JSR print_string 2458 LDX #20 2459 .swap_pointers 2460 LDA old_name_store,X 2461 PHA 2462 LDA new_name_store,X 2463 STA old_name_store,X 2464 PLA 2465 STA new_name_store,X 2466 DEX 2467 BPL swap_pointers 2468 LDA olen 2469 PHA 2470 LDA nlen 2471 STA olen 2472 PLA :PLA :PLA 2473 RTS 2474 .old_name_store 2475 EQUS" " 2476 .new_name_store 2477 EQUS" " 2478 .old_prompt 2479 EQUB 13 2480 EQUS"Variable : " 2481 EQUB 255 2482 .new_prompt 2483 EQUB 13 2484 EQUS"Replace with :" 2485 EQUB 255 2486 .error1 2487 EQUB 13 2488 EQUS"Err1" 2489 EQUW &FF07 2490 .error2 2491 EQUB 13 2492 EQUS"Err2" 2493 EQUW &FF07 2494 .done 2495 EQUS" occurence(s) replaced" 2496 EQUW &FF0D 2497 .number EQUB 0 2498 ] :NEXT pass 2499 ENDPROC
Program 7.1. PROCgrepl - a global search and replace facility
Lines 2233 to 2241: Otherwise move on past new line header bytes and force branch to 'update4' .
Lines 2243 to 2244: Test for quotes and branch if not there.
Lines 2245 to 2249: Locate the end pair of quotes.
Lines 2250 to 2252: If ASCII return found first, branch to 'update4' .
Lines 2254 to 2255: If a hexadecimal value is indicated, branch.
Lines 2256 to 2261: Check for a valid variable character.
Lines 2263 to 2272: Compare old variable name with the string pointed to in the program by 'current' . Exit on first unlike character.
Line 2273: If negative strings compared force a branch to 'insert_new'.
Lines 2274 to 2285: String not found so update all pointers and redo from 'main_loop' .
Lines 2286 to 2291: Increment occurrence pointer and update pointers.
Lines 2292 to 2298: Add new line length to the '}ink' byte. If link byte is greater than permissible value then perform 'bad_string' error. Else go to 'leap_frog' .
Lines 2299 to 2304: Calculate if space occupied by variable needs to be altered, if so, move distal portion of program up or down memory
as required.
Lines 2305 to 2325: Open up the program at the variable name to make way for a longer variable name.
Lines 2326 to 2334: Write new variable name over the old variable name.
Lines 2335 to 2346: Close up variable space by desired amount to ensure that new shorter variable name fits correctly, then overwrite it.
Lines 2347 to 2352: Update current position in program vector.
Lines 2353 to 2375: Check that 'current' contents being investigated is a legal variable value.
Lines 2376 to 2387: Print the ASCII character string pointed to by the address held in the Index registers. Printing is terminated on encountering a negative byte, typically &FF.
Lines 2388 to 2415: Input an ASCII character string up to 20 characters long and store it in the buffer pointed to by the index registers.
Lines 2416 to 2420: Perform DELETE
Lines 2421 to 2425: Execute 'Too big' error.
Lines 2426 to 2429: Handle ESCAPE.
Lines 2430 to 2453: Print number of occurrences after first converting it into an ASCII-based decimal number.
Lines 2454 to 2457: Print bad string error message.
Lines 2458 to 2473: Reset pointers to former values and exit to BASIC.
Lines 2474 to 2497: ASCII string storage area.
To use GREPL press f0 and answer to the prompts as they appear. The new variable name may be up to 20 characters long; variables greater than this are not accepted. Once the replace name is entered the program goes about its business and the number of occurrences/ replacements are indicated on completion.
Event | Cause | |
0 | Output buffer empty | |
1 | Input buffer full | |
2 | Character for input buffer entering | |
3 | ADC conversion finished | |
4 | Vertical sync start | |
5 | Interval timer crossing zero | |
6 | ESCAPE detected | |
7 | RS 423 error | |
8 | Econet event detected | |
9 | User event detected | |
10 REM *** Continuous display clock * ** 20 REM *** redirects EVENTV vector * ** 30 *FX13,5 40 CLS 50 PRINTCHR$141;" The Mute Clock!" 60 PRINTCHR$141;" The Mute Clock!"' '' 70 INPUT"Hour :"H% 80 INPUT"Minute :"M% 90 INPUT"Seconds :"S% 100 PROCtime(H%,M%,S%,&A00) 110 PRINT'' 120 PRINT"You have set the time for:"; 130 PRINT" ";H%;":";M%;":";S%'' 140 PRINT"Press key to start clock" 150 key=GET 160 CALL &A00 170 END 180 : 2500 DEF PROCtime(gethrs,getmins,getsec s,addr) 2501 FOR pass=0 TO 2 STEP 2 2502 P%=addr 2503 [ OPT pass 2504 JMP setup 2505 .tick_clock 2506 PHP 2507 PHA 2508 TXA:PHA 2509 TYA 2510 PHA 2511 LDA#4 2512 LDY#clock DIV 256 2513 LDX#clock MOD 256 2514 JSR &FFF1 2515 INC seconds 2516 LDA seconds 2517 CMP#60 2518 BNE over 2519 LDA#0 2520 STA seconds 2521 INC minutes 2522 LDA minutes 2523 CMP#60 2524 BNE over 2525 LDA#0 2526 STA minutes 2527 INC hours 2528 LDA hours 2529 CMP#24 2530 BNE over 2531 LDA#0 2532 STA hours 2533 .over 2534 LDA hours 2535 LDY#72 2536 JSR display 2537 LDA#ASC":" 2538 STA HIMEM,Y 2539 INY 2540 LDA minutes 2541 JSR display 2542 LDA #ASC":" 2543 STA HIMEM,Y 2544 INY 2545 LDA seconds 2546 JSR display 2547 PLA 2548 TAY 2549 PLA 2550 TAX 2551 PLA 2552 PLP 2553 RTS 2554 .display 2555 LDX#0 2556 SEC 2557 .loop 2558 SBC#10 2559 BMI no_jump 2560 INX 2561 JMP loop 2562 .no_jump 2563 DEX 2564 CLC 2565 ADC#58 2566 PHA 2567 TXA 2568 ADC#48 2569 STA HIMEM,Y 2570 INY 2571 PLA 2572 STA HIMEM,Y 2573 INY 2574 RTS 2575 2576 .setup 2577 LDA #gethrs 2578 LDX #getmins 2579 LDY #getsecs 2580 STA hours 2581 STX minutes 2582 STY seconds 2583 LDA#22 2584 JSR &FFEE 2585 LDA#7 2586 JSR &FFEE 2587 LDA#28 2588 JSR &FFEE 2589 LDA#0 2590 JSR &FFEE 2591 LDA#24 2592 JSR &FFEE 2593 LDA#39 2594 JSR &FFEE 2595 LDA#2 2596 JSR &FFEE 2597 LDA#tick_clock MOD 256 2598 STA&220 2599 LDA#tick_clock DIV 256 2600 STA &221 2601 JSR tick_clock 2602 LDA#14 2603 LDX#5 2604 JSR &FFF4 2605 RTS 2606 .clock 2607 EQUD &FFFFFF9C 2608 EQUB &FF 2609 .hours EQUB 0 2610 .minutes EQUB 0 2611 .seconds EQUB 0 2612 ] 2613 NEXT 2614 ENDPROC
Program 8.1. PROCtime - a background digital clock
The initial program call to 'setup' (line 2504) does a number of things. First, it loads the hours, minutes and seconds values previously input into their respective counters (lines 2577 to 2582). A MODE 7 screen is then selected and a text window defined to ensure that the digital clock cannot be scrolled off the screen. Lines 2597 to 2600 reset the EVNTV vector to point to the 'tick_tock' routine at line 2505. The final lines (lines 2602 and 2603) perform an *FX14,5 which balances the previous *FX13,5 (line 30). These two calls disable and enable the interval timer crossing zero event.
The rest of the program's operation is straightforward. Each time the event occurs 'tick_tock' is entered and the interval timer reset to count a further second (lines 2511 to 2514). Note that on entry to the routine all processor registers are preserved. This is very important, otherwise the processor would probably crash when it returned to take up the task it was undertaking before the event occurred. Lines 25 15 to 2532 simply update the seconds, minutes and hours counters as required. The code between lines 2534 to 2546 stores the latest clock value at the top left-hand corner of the screen. The display subroutine (line 2554) called by the program performs a simple hex to decimal ASCII conversion by continually subtracting 10 from the value to be displayed.
Finally, the processor registers are restored (lines 2547 to 2552) before control is transferred back to the interrupted program.
10 ON ERROR GOTO 5000
At line 5000, the error message and line can be printed out. The problem still remains that the erroneous line is not listed, nor is the source of the error listed.
Program 9.1 solves both these problems. After being set up and installed, errors occurring at run-time will be treated in the normal manner except that the line containing the error will be listed starting at the point of the error, thus highlighting the mistake. For example, the program line
10 PRINT"HELLO" : STUPID ERROR : VDU 7
would normally result in the error:
Mistake at line 10
at run-time. With the new error lister inserted, the response would be
STUPID ERROR : VDU 7
Mistake at line 10
10 REM *** ERROR LISTER *** 20 PROCerror (&C00) 30 *KEY0 CALL &C00|M 40 *KEY1 CALL &C24|M 50 END 60 : 2620 DEF PROCerror (addr) 2621 FOR pass=0 TO 3 STEP3 2622 brkv=?&202+(?&203*256) 2623 P%=addr 2624 [ 2625 OPT pass 2626 .SETUP 2627 LDX #0 2628 .next_chr 2629 LDA message,X 2630 JSR &FFE3 2631 INX 2632 CMP #13 2633 BNE next_chr 2634 LDA &202 2635 STA address 2636 LDA &203 2637 STA address+1 2638 LDA #entry MOD 256 2639 STA &202 2640 LDA #entry DIV 256 2641 STA &203 2642 RTS 2643 .restore 2644 LDX #0 2645 .next_chr 2646 LDA message2,X 2647 JSR &FFE3 2648 INX 2649 CMP#13 2650 BNE next_chr 2651 LDA address 2652 STA &202 2653 LDA address+1 2654 STA &203 2655 RTS 2656 .entry 2657 BIT &FF 2658 BMI was_esc 2659 CLC 2660 LDA &1B 2661 ADC &39 2662 TAX 2663 LDY #0 2664 JSR &FFE7 2665 .next_error 2666 LDA (&19),Y 2667 CMP #13 2668 BEQ was_esc 2669 CMP #32 2670 BCC garbage 2671 CMP #&80 2672 BCS garbage 2673 JSR &FFEE 2674 .garbage 2675 INY 2676 DEX 2677 BNE next_error 2678 .was_esc 2679 JMP brkv 2680 .message 2681 EQUS" Error Lister On!" 2682 EQUB 7 2683 EQUB 13 2684 .message2 2685 EQUS" Error Lister Off!" 2686 EQUB 7 2687 EQUB 13 2688 .address 2689 EQUS" " 2690 ] 2691 NEXT 2692 ENDPROC
Program 9.1. PROCerror - lists the program line in which an error occured.
The section of line which created the mistake has been listed in addition to the normal error message.
The assembled program occupies just 141 bytes and is completely self-contained so that it can be tucked out of the way during debugging. As with other programs of this type in the Portfolio, there are two entry points - to switch the lister on (entry at line 2626) and off (entry at line 2643). The 'setup' section of code saves the normal contents of BRKV at &202 and revectors it to point to 'entry' at line 2656.
When the interpreter causes the program to abort via BRKV the new wedge coding is executed. It begins first at line 2657 by testing bit 7 of location &FF. If this bit is set then the abortion was due to the ESCAPE key being pressed and so the normal 'brkv' is jumped to. Assuming that ESC was not pressed, the length of the current expression being evaluated by the interpreter, and the one that caused the error to occur, is calculated. The bytes at &1B and &39 are summed (lines 2659 to 2662) and the result moved into the X register Location &1B contains the current offset for the expression evaluation pointer while &39 contains the actual length of the expression.
The address of the current expression is held in the vector at &19 which is known as the expression evaluation base pointer, and each byte is in turn accessed and printed to the screen (line 2666). If a carriage return is encountered, the end of the line has been reached and the program jumps to the normal 'brkv' for the printing of tht error message (lines 2667 and 2668). The comparisons of lines 2669 and 2671 ensure that no garbage gets printed to the screen, should the program crash have caused any to have been poked into the program inadvertently.
10 REM *** SPACE & REM REMOVER *** 20 PROCpack (&70,&72,&C00) 30 END 40 : 2700 DEF PROCpack (current,new_position ,addr) 2701 FOR pass=0 TO 3 STEP3 2702 P%=addr 2703 [ 2704 OPT pass 2705 LDA #0 2706 STA new_position 2707 STA current 2708 LDA &18 2709 STA new_position+1 2710 STA current+1 2711 .outer 2712 LDA #0 2713 STA rem_flag 2714 LDY #1 2715 JSR transfer 2716 CMP #&FF 2717 BEQ all_done 2718 JSR transfer 2719 JSR transfer 2720 .inner 2721 LDA (current),Y 2722 BIT rem_flag 2723 BPL flag_clear 2724 CMP #13 2725 BNE space 2726 JSR transfer 2727 BEQ end_of_line 2728 .flag_clear 2729 CMP #ASC" " 2730 BEQ space 2731 CMP #&F4 2732 BNE not_rem 2733 DEY 2734 LDA #&FF 2735 STA rem_flag 2736 BNE space 2737 .not_rem 2738 JSR transfer 2739 BEQ end_of_line 2740 CMP #&22 2741 BEQ inside_quote 2742 BNE inner 2743 .space 2744 INC current 2745 BNE inner 2746 INC current+1 2747 BNE inner 2748 .end_of_line 2749 DEY 2750 TYA 2751 PHA 2752 CPY #3 2753 BEQ clear 2754 LDY #3 2755 STA (new_position),Y 2756 CLC 2757 ADC new_position 2758 STA new_position 2759 BCC clear 2760 INC new_position+1 2761 .clear 2762 PLA 2763 CLC 2764 ADC current 2765 STA current 2766 BCC outer 2767 INC current+1 2768 BNE outer 2769 .inside_quote 2770 JSR transfer 2771 BEQ end_of_line 2772 CMP #&22 2773 BNE inside_quote 2774 BEQ inner 2775 .all_done 2776 LDA new_position 2777 CLC 2778 ADC #2 2779 STA &12 2780 LDA new_position+1 2781 ADC #0 2782 STA &13 2783 RTS 2784 .transfer 2785 LDA (current),Y 2786 STA (new_position),Y 2787 INY 2788 CMP #13 2789 RTS 2790 .rem_flag 2791 EQUS " " 2792 ] 2793 NEXT 2794 ENDPROC
Program 9.2. PROCpack - a space and REM remover.
The space and REM tests are performed in lines 2729 and 2731 respectively and the corresponding branch made accordingly. Ifa space is detected, the 'current' vector is incremented, no change is made to the 'new_position' vector and the space is not transferred. Thus, effectively the space gets lost as illustrated in Figure 9.1. The REM test looks for the token for REM which is &F4. If the token is found &FF is placed in the 'rem_flag' to indicate this - so that the program knows it is within a REM statement and is, in fact, 'deleting' items from the line rather than transferring them. A branch to 'space' (line 2736) increments the 'current' vector before a branch to 'inner' is forced.
Fig. 9.1. Overwriting bytes to compact a program.
Figure 9.1. Overwriting bytes to compact a program
The relevant instruction here is in line 2722, where the 'rem_flag' is tested with BIT. If the flag is clear, the following branch is executed, otherwise the 'current' vector is incremented via 'space' .This entire process continues until the end of line return character is encountered (line 2724). The 'end_of_line' routine (line 2748) rapidly transfers the three-byte line header, as comparing this would be an utter waste of processor time.
Occasionally, spaces are required by programs. The most obvious occasion is within ASCII strings where they are used for formatting text. The 'inside_quote' coding ensures that any spaces occurring within the boundary of quotes are not removed. This section is entered via line 2741.
0 REM!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!! 10 PROCautorun 20 END 30 : 2800 DEF PROCautorun 2801 *FX247,76 2802 *FX248,6 2803 A%=249 2804 Y%=0 2805 X%=PAGE DIV 256 2806 CALL &FFF4 2807 P%=PAGE+6 2808 [ 2809 LDA #138 2810 LDX #0 2811 LDY #ASC("O") 2812 JSR &FFF4 2813 LDY #ASC("L") 2814 JSR &FFF4 2815 LDY #ASC("D") 2816 JSR &FFF4 2817 LDY #14 2818 DEY 2819 JSR &FFF4 2820 LDY #ASC("R") 2821 JSR &FFF4 2822 LDY #ASC("U") 2823 JSR &FFF4 2824 LDY #ASC("N") 2825 JSR &FFF4 2826 LDY #14 2827 DEY 2828 JSR &FFF4 2829 RTS 2830 ] 2831 ENDPROC
Program 9.3. PROCautorun - will automatically run a program no matter
what!
The assembler is quite straightforward: an *FX 138 call is used to place the string "OLD<RETURN> RUN<RETURN>" into the keyboard buffer. It is not possible, however, to use this call to poke a return character, ASCII 13, into the buffer. To get round this, the Y register is loaded with 14 and then decremented (lines 2817 and 2826). The machine code is assembled in a rather strange place - in fact, it overwrites the 50 exclamation marks after the REM statement in line 0. As you can see, P% is set to PAGE+6 in line 2807. If you run the program then list it you will see that the !s are replaced by gobbledygook; this is just the interpreter trying to de-tokenise the machine code. These will have no effect when the program is run as they are away from the program, hiding behind the REM statement. The magic part of the program comes in lines 2801 to 2806. Here the BREAK intercept codes controlled by *FX247, *FX248 and *FX249 are rewritten to print the BRK handler to the code now stored at PAGE+6, so whenever any sort of BREAK is performed the interpreter comes here and OLDs and re-RUNs the program. Once the program has been run, only line 0 need remain; the others can be deleted as required.
Program 9.2
Procedure title : PROCpack
Variables requircd : addr
Line numbers : 2700 to 2794
Length : 149 bytes
Zero page requirements : 4 bytes
Registers changed : A, X, Y
Program 9.3
Procedure title : PROCautorun
Variables required : addr
Line numbers : 2800 to 2831
Length : 53 bytes (inside program)
Zero page requirements : none
Registers changed : A, X, Y
The programs provided in this chapter are as follows:
Program 10.1 | : Multi-byte addition. |
Program 10.2 | : Multi-byte subtraction. |
Program 10.3 | : Multi-byte multiplication. |
Program 10.4 | : Multi-byte division. |
Program 10.5 | : Single-byte square root. |
Program 10.6 | : Double-byte square root. |
Program 10.7 | : Double-byte ASL. |
Program 10.8 | : Double-byte LSR. |
Program 10.9 | : Double-byte ROR. |
Program 10.10 | : Double-byte ROL. |
Program 10.11 | : Multi-byte ASL. |
10 REM *** MULTI-BYTE ADDITION *** 20 PROCmulti_add(&70,&71,&73,&C00) 30 @%=0 40 ?&70=4 50 !&71=&4000 60 !&73=&4100 70 !&4000=123456 80 !&4100=123456 90 PRINT'''»123456+123456="; 100 CALL mbadd 110 PRINT!&4000 120 END 130 : 5000 DEF PROCmulti_add(count,first,seco nd,addr) 5001 FOR PASS=0 TO 3 STEP 3 5002 P%=addr 5003 [ 5004 OPT PASS 5005 .mbadd 5006 LDX count 5007 LDY #0 5008 CLC 5009 .next_byte 5010 LDA (first),Y 5011 ADC (second),Y 5012 STA (first),Y 5013 INY 5014 DEX 5015 BNE next_byte 5016 RTS 5017 ] 5018 NEXT 5019 ENDPROC
Program 10.1. PROCmulti_add - adds two multi-byte numbers together
10 REM *** MULTI-BYTE SUBTRACTION *** 20 PROCmulti_sub(&70,&71,&73,&C00) 30 @%=0 40 ?&70=4 50 !&71=&4000 60 !&73=&4100 70 !&4000=123456 80 !&4100=3456 90 PRINT'''»123456-3456="; 100 CALL mbsub 110 PRINT!&4000 120 END 130 : 5030 DEF PROCmulti_sub(count,first,seco nd,addr) 5031 FOR PASS=0 TO 3 STEP 3 5032 P%=addr 5033 [ 5034 OPT PASS 5035 .mbsub 5036 LDX count 5037 LDY #0 5038 SEC 5039 .next_byte 5040 LDA (first),Y 5041 SBC (second),Y 5042 STA (first),Y 5043 INY 5044 DEX 5045 BNE next_byte 5046 RTS 5047 ] 5048 NEXT 5049 ENDPROC
Program 10.2. PROCmulti_sub - subtracts one multi-byte number from
another.
10 REM *** MULTI-BYTE MULTIPLICATION *** 20 PROCmulti_mult (&70,&72,&74,&75,&4 000,&4200) 30 !&70=&3000 40 !&72=&3100 50 ?&74=4 60 !&3000=1234 70 !&3100=1234 80 CALL mb_mult 90 PRINT»Result of multiplication :"; 100 PRINT!&3000 110 END 120 : 5050 DEF PROCmulti_mult(first,second,to tlen,count,buffer,addr) 5051 FOR pass=0 TO 3 STEP 3 5052 P%=addr 5053 [ 5054 OPT pass 5055 .mb_mult 5056 LDA second 5057 SEC 5058 SBC #1 5059 STA second 5060 LDA second+1 5061 SBC #0 5062 STA second+1 5063 LDA first 5064 SEC 5065 SBC #1 5066 STA first 5067 LDA first+1 5068 SBC #0 5069 STA first+1 5070 LDA totlen 5071 BEQ finished 5072 STA count 5073 LDA #0 5074 ASL count 5075 ROL A 5076 ASL count 5077 ROL A 5078 ASL count 5079 ROL A 5080 STA count+1 5081 INC count 5082 BNE over 5083 INC count+1 5084 .over 5085 LDX totlen 5086 LDA #0 5087 .save_loop 5088 STA buffer-1,X 5089 DEX 5090 BNE save_loop 5091 CLC 5092 .loop 5093 LDX totlen 5094 .rotate_loop 5095 ROR buffer-1,X 5096 DEX 5097 BNE rotate_loop 5098 LDY totlen 5099 .rotate_save 5100 LDA (first),Y 5101 ROR A 5102 STA (first),Y 5103 DEY 5104 BNE rotate_save 5105 BCC no_add 5106 LDY #1 5107 LDX totlen 5108 CLC 5109 .add_loop 5110 LDA (second),Y 5111 ADC buffer-1,Y 5112 STA buffer-1,Y 5113 INY 5114 DEX 5115 BNE add_loop 5116 .no_add 5117 DEC count 5118 BNE loop 5119 LDX count+1 5120 BEQ finished 5121 DEX 5122 STX count+1 5123 JMP loop 5124 .finished 5125 RTS 5126 ] 5127 NEXT 5128 ENDPROC
Program 10.3. PROCmulti_mult - multiplies two multi-byte numbers
The program operates as follows:
Lines 5056 to 5062: Subtract 1 from address of 'second'.
Lines 5063 to 5069: Subtract 1 from address of 'first'.
Lines 5070 to 5071: If total length is zero then end.
Lines 5072: Set number of bytes to count.
Lines 5073 to 5079: Multiply count by eight.
Lines 5080 to 5083: Add one to value of count.
Lines 5084 to 5091: Save high product in buffer.
Lines 5092 to 5098: Shift carry bit into buffer and bit 0 of high product into the carry flag.
Lines 5099 to 5104: Rotate carry into most significant bit of 'first' and shift next bit of multiplier into the carry flag.
Line 5105: Carry clear so no addition required.
Lines 5106 to 5115: Carry flag is set so add *second' and high product together.
Lines 5116 to 5123: Decrement bit count and exit if zero, else repeat for the next bit.
The lines of BASIC show how the routine needs to be set up before calling it. In a larger assembler program these introductory peeks and pokes would be performed using assembler and the multiplication routine called as a subroutine from the main program. Lines 30 and 40 place the two addresses of data into the zero page vectors, while lines 60 and 70 place the values to be multiplied (both 1234) into these data buffers. Previously, in line 50, the total number of bytes to be combined, four (1234 can be held in two bytes), is poked into location &74 which corresponds to the variable 'totlen' in the procedure. After calling the subroutine (line 80) the final result is displayed. Check it on a calculator if you wish!
10 REM *** MULTI-BYTE DIVISION *** 20 PROCmult_div(&70,&72,&74,&75,&77,& 79,&3000,&3100,&4000) 30 !&70=&3400 40 !&72=&3500 50 ?&74=3 60 !&3400=10000 70 !&3500=2 80 CALL &4000 90 PRINT »Result is :"; 100 PRINT!&3400 110 PRINT»Remainder :"; 120 PRINT!&3000 130 END 140 : 5150 DEF PROCmult_div(first,second,totl en,count,hidiv_pointer,pointer,buffer1,b uffer2,addr) 5151 FOR pass=0 TO 3 STEP 3 5152 P%=addr 5153 [ 5154 OPT pass 5155 .multi_div 5156 LDA totlen 5157 BNE begin 5158 JMP okay_out 5159 .begin 5160 STA count 5161 LDA #0 5162 ASL count 5163 ROL A 5164 ASL count 5165 ROL A 5166 ASL count 5167 ROL A 5168 STA count+1 5169 INC count 5170 BNE over 5171 INC count+1 5172 .over 5173 LDX totlen 5174 LDA #0 5175 .clear 5176 STA buffer1-1,X 5177 STA buffer2-1,X 5178 DEX 5179 BNE clear 5180 LDA #buffer1 MOD 256 5181 STA hidiv_pointer 5182 LDA #buffer1 DIV 256 5183 STA hidiv_pointer+1 5184 LDA #buffer2 MOD 256 5185 STA pointer 5186 LDA #buffer2 DIV 256 5187 STA pointer+1 5188 LDX totlen 5189 LDY #0 5190 TYA 5191 .check 5192 ORA (second),Y 5193 INY 5194 DEX 5195 BNE check 5196 CMP #0 5197 BNE divide 5198 JMP error 5199 .divide 5200 CLC 5201 .set_loop 5202 LDX totlen 5203 LDY #0 5204 .loop 5205 LDA (first),Y 5206 ROL A 5207 STA (first),Y 5208 INY 5209 DEX 5210 BNE loop 5211 .dec_count 5212 DEC count 5213 BNE set_shift 5214 LDX count+1 5215 BEQ okay_out 5216 DEX 5217 STX count+1 5218 .set_shift 5219 LDX totlen 5220 LDY #0 5221 .shift_loop 5222 LDA (hidiv_pointer),Y 5223 ROL A 5224 STA (hidiv_pointer),Y 5225 INY 5226 DEX 5227 BNE shift_loop 5228 LDY #0 5229 LDX totlen 5230 SEC 5231 .subtract 5232 LDA (hidiv_pointer),Y 5233 SBC (second),Y 5234 STA (pointer),Y 5235 INY 5236 DEX 5237 BNE subtract 5238 BCC set_loop 5239 LDY hidiv_pointer 5240 LDX hidiv_pointer+1 5241 LDA pointer 5242 STA hidiv_pointer 5243 LDA pointer+1 5244 STA hidiv_pointer+1 5245 STY pointer 5246 STX pointer+1 5247 JMP set_loop 5248 .okay_out 5249 CLC 5250 BCC finished 5251 .error 5252 SEC 5253 .finished 5254 RTS 5255 ] 5256 NEXT 5257 ENDPROC
Program 10.4. PROCmulti_div - divides one multi-byte number by another
The total number of bytes to be referenced in the division is placed in 'totlen' prior to the call. On exit from the routine, the carry flag bit is set if an error occurred during the division - otherwise it returns clear. The program operates as follows:
Lines 5156 to 5158: Get 'totlen' if zero then perform a no error finish.
Lines 5160 to 5166: Set count and then multiply by 8 to obtain total number of bits to do.
Lines 5167 to 5171: Add one to bit counter.
Lines 5173 to 5179: Initialise the high dividend result buffer to zero.
Lines 5180 to 5187: Point vectors to buffers.
Lines 5188 to 5198: Check that the divisor, held in 'second' is not
zero!
Lines 5199 to 5200: Clear carry flag on entry into 'divide'.
Lines 5201 to 5210: Move the carry flag bit into the low dividend, 'first', to use as the next quotient bit. Then move the most significant bit of the low dividend into the carry flag bit.
Lines 5211 to 5217: Decrement ^count' by one and branch to 'okay_out' if all bits are done.
Lines 5218 to 5227: Transfer the carry flag bit into the least significant bit of the high dividend.
Lines 5228 to 5237: Subtract 'second' from high dividend and save result at 'pointer'.
Lines 5238 to 5246: If the carry flag bit is set then the trial subtraction worked! Therefore, set the quotient bit and switch pointers to replace remainder and dividend. If the carry flag is clear, the trial subtraction failed; therefore skip swap over and branch direct as next quotient bit is zero.
Line 5247: Do next bit.
Lines 5248 to 5250: Finished with no errors detected.
Lines 5251 to 5254: Finished with an error present.
Once again, the first few programs lines show how the procedure can be set up, using BASIC. The procedure is assembled passing workspace, vectors and buffer locations as parameters (line 20). The two vectors at 'first' and 'second' are poked with buffer addresses (lines 30 and 40), which are subsequently seeded with the dividend and divisor (lines 60 and 70). The dividend is 10000 and the divisor 2, which require a total of three bytes' storage, as indicated in line 50 which pokes the byte count into 'totlen'.
After calling the routine the result is extracted from the buffer at &3400 and the remainder from the buffer at &3000.
Finding the square root of a number in machine code might at first sight seem rather difficult. However, there is a quite straightforward solution. The method is simply this: 'the square root of an integer number is equal to the total number of successively higher odd integer numbers that can be subtracted from it'. Consider the number 36: first we subtract one from it, then three, then five and so on until we have no remainder. The total number of odd numbers subtracted is its square root! Thus,
36-1=35 | : partial root = 1 | |
35-3=32 | : partial root = 2 | |
32-5=27 | : partial root = 3 | |
27-7=20 | : partial root = 4 | |
20-9=11 | : partial root = 5 |
11-11=0 | : final square root = 6 |
10 REM ***SINGLE BYTE SQUARE ROOT*** 20 PROConebyte_square(&70,&C00) 30 ?&70=170 40 CALL square 50 PRINT»Square root =";?&70 60 PRINT»remainder =";?&71 70 END 80 : 5270 DEF PROConebyte_square(byte,addr) 5271 FOR pass=0 TO 3 STEP 3 5272 P%=addr 5273 [ 5274 OPT pass 5275 .square 5276 LDY #0 5277 LDA #1 5278 STA byte+1 5279 LDA byte 5280 .loop 5281 CMP byte+1 5282 BCC finished 5283 SBC byte+1 5284 INY 5285 INC byte+1 5286 INC byte+1 5287 JMP loop 5288 .finished 5289 STY byte 5290 STA byte+1 5291 RTS 5292 ] 5293 NEXT 5294 ENDPROC
Program 10.5. PROConebyte_square - calculates the square root of a single-byte number.
Program 10.5 is simple but effective. On entry to 'square' (line 5275) the Y register is initialised ready to take the partial root count; the accumulator is loaded with the first odd number to be subtracted which is then written to the location 'byte+1' (lines 5276 to 5279). The subtract and count loop is embodied in lines 5280 to 5287. Line 5281 begins by comparing the contents from byte (the current remainder) with the next odd number, a clear carry flag denotes that the remainder is less than the next odd number and the program branches to 'finish'. A set carry and line 5283 subtracts the current odd number from the current remainder (line 5283) and the Y register is incremented. Before the loop is redone the two is added to the contents of 'byte+1' to move onto the next odd number (lines 5285 and 5286). Note that the program passes the immediate value through to the procedure for splitting into two bytes and storing in 'block' before the shift is performed. If the value is already held in 'block' then 'do-asl' can be called directly.
Program 10.6 is a double-byte version, Program 10.5 finding the square root of an unsigned 16-bit integer value. The two locations at 'byte' hold the integer value while 'temp' counts the double-byte odd number. The program operates virtually the same as its predecessor; but because a two-byte value is involved a subtraction rather than a compare must be performed initially. To ensure that the final subtraction will not erode any remainder, its possible low order byte is preserved in the X register. Looking further down the program listing (line 5322) it seems at first sight that the odd number counter is only being incremented by one. However, two is actually being added as the carry flag will be set at this point, if it is clear where the branch to 'finish' at line 5319 would have been performed.
10 REM *** TWO BYTE SQUARE ROOT *** 20 PROCtwobyte_square(&70,&72,&C00) 30 !&70=1234 40 CALL two_square 50 PRINT»Square root =";?&70 60 PRINT»remainder =";?&71 70 END 80 : 5300 DEF PROCtwobyte_square(byte,temp,a ddr) 5301 FOR pass=0 TO 3 STEP 3 5302 P%=addr 5303 [ 5304 OPT pass 5305 .two_square 5306 LDY #1 5307 STY temp 5308 DEY 5309 STY temp+1 5310 .loop 5311 SEC 5312 LDA byte 5313 TAX 5314 SBC temp 5315 STA byte 5316 LDA byte+1 5317 SBC temp+1 5318 STA byte+1 5319 BCC finished 5320 INY 5321 LDA temp 5322 ADC #1 5323 STA temp 5324 BCC loop 5325 INC temp+1 5326 .finished 5327 STY byte 5328 STX byte+1 5329 RTS 5330 ] 5331 NEXT 5332 ENDPROC
Program 10.6. PROCtwobyte_square - calculates the square root of a 16-bit value.
ASL byte
ASL byte+1
is used.
To perform an overall ASL on two bytes, the initial ASL must be followed by a ROL. Program 10.7 illustrates the technique while Figure 10.1 shows what is happening. The ASL of line 5359 moves bit 7 of 'block+1' (the low byte in true 6502 back-to-frontness!) into the carry inserting a 0 into bit 0. Line 5360 then performs a ROL which moves bit 7 in the carry into bit 0 of 'block', shuffling the internal bits up one bit. The last bit, bit 7, falls out into the carry. The two-byte value has also been multiplied by two!
10 REM*** DOUBLE BYTE ASL *** 20 PROCtwo_byte_asl(1,&70,&C00) 30 CALLset_asl 40 FOR loop=1 TO 15 50 PRINT?&70*256+?&71 60 CALLdo_asl 70 NEXT loop 80 END 90 : 5350 DEF PROCtwo_byte_asl(num,block,add r) 5351 P%=addr 5352 [ 5353 .set_asl 5354 LDA #num MOD 256 5355 STA block 5356 LDA #num DIV 256 5357 STA block+1 5358 .do_asl 5359 ASL block+1 5360 ROL block 5361 RTS 5362 ] 5363 ENDPROC
Program 10.7. PROCtwo_byte_asl - arithmetical shift left on a 16-bit value.
Fig. 10.1. Implementing a 16-bit shift register using an ASL/ROL
Program 10.8 works in the opposite direction performing an overall LSR using an LSR and ROR in conjunction. As the shift works in the opposite direction the bytes are referenced in the opposite order to an ASL. The shift is performed on the low byte in 'block' and the rotate on the high byte in 'block+1'. The total effect is to halve the two-byte number.
10 REM*** DOUBLE BYTE LSR *** 20 PROCtwo_byte_lsr(65535,&70,&C00) 30 CALLset_lsr 40 FOR loop=1 TO 15 50 PRINT?&70*256+?&71 60 CALLdo_lsr 70 NEXT loop 80 END 90 : 5370 DEF PROCtwo_byte_lsr(num,block,add r) 5371 P%=addr 5372 [ 5373 .set_lsr 5374 LDA #num DIV 256 5375 STA block 5376 LDA #num MOD 256 5377 STA block+1 5378 .do_lsr 5379 LSR block 5380 ROR block+1 5381 RTS 5382 ] 5383 ENDPROC
Program 10.8. PROCtwo_byte_lsr - logical shift right on a 16-bit value.
Programs 10.9 and 10.10 perform double-byte RORs and ROLs respectively. The only difference to note here is the order in which the bytes in 'block' are rotated. In RORing a two-byte number, the low byte is rotated first. With a ROL it is the high byte that is manipulated first.
10 REM*** DOUBLE BYTE ROR *** 20 PROCtwo_byte_ror(32768,&70,&C00) 30 CALLset_ror 40 FOR loop=1 TO 15 50 PRINT?&70*256+?&71 60 CALLdo_ror 70 NEXT loop 80 END 90 : 5390 DEF PROCtwo_byte_ror(num,block,add r) 5391 P%=addr 5392 [ 5393 .set_ror 5394 LDA #num MOD 256 5395 STA block 5396 LDA #num DIV 256 5397 STA block+1 5398 .do_ror 5399 ROR block 5400 ROR block+1 5401 RTS 5402 ] 5403 ENDPROC
Program 10.9. PROCtwo_byte_ror - a double-byte rotate right.
10 REM*** DOUBLE BYTE ROL *** 20 PROCtwo_byte_rol(1,&70,&C00) 30 CALLset_rol 40 FOR loop=1 TO 15 50 PRINT?&70*256+?&71 60 CALLdo_rol 70 NEXT loop 80 END 90 : 5410 DEF PROCtwo_byte_rol(num,block,add r) 5411 P%=addr 5412 [ 5413 .set_rol 5414 LDA #num MOD 256 5415 STA block 5416 LDA #num DIV 256 5417 STA block+1 5418 .do_rol 5419 ROL block 5420 ROL block+1 5421 RTS 5422 ] 5423 ENDPROC
Program 10.10. PROCtwo_byte_rol - a double-byte rotate left.
Finally, Program 10.11 shows how a multi-byte shift, left in this instance, can be implemented on an unsigned value between 2 and 255 bytes in length. The start of the bytes to be shifted are located in 'start' while the number of them is found in 'bytes'. The program commences by placing the 'bytes' count into the Y register and performing the initial ASL on 'start' (lines 5434 to 5436). The X register is loaded with one and after decrementing tlne Y register the 'next' loop is entered (lines 5436 to 5438). From here on, indexed addressing is used to facilitate the ROL on the remaining bytes. The first handful of lines in the program point out the sort of interesting, and perhaps useful (!) applications the program can be used for. The test of line 50 is printed onto the MODE 6 screen before the memory is used to hold the test is shifted left twice (lines 60 and 70). The net effect is to provide 3D text!
10 REM *** MULTI LEFT SHIFT *** 20 REM *** GIVES 3D CHARACTERS *** 30 PROCmulti_left (&6000,64,&C00) 40 MODE 6 50 PRINT» HELLO THERE!!" 60 CALL&C00 70 CALL&C00 80 END 90 : 5430 DEF PROCmulti_left (start,bytes,ad dr) 5431 FOR PASS=0 TO 3 STEP3 5432 P%=addr 5433 [ :OPT PASS 5434 LDY bytes 5435 ASL start 5436 LDX #1 5437 DEY 5438 .next 5439 ROL start,X 5440 INX 5441 DEY 5442 BNE next 5443 RTS 5444 ] 5445 NEXT 5446 ENDPROC
Program 10.11. PROCmulti_left - performs an arithmetic shift left on a
multi-byte number.
Program 10.2
Procedure title : PROCmulti_sub
Variables required : count, first, second, addr
Line numbers : 5030 to 5049
Length : 16 bvtes
Zero page requiremcnts : 5 bytes
Registers changed : A, X, Y
Program 10.3
Procedure title : PROCmulti_mult
Variables required : first, sccond, totlen, count buffer, addr
Line numbers : 5050 to 5128
Length : 114 bytes
Zero page requirements: : 6 bytes
Registers changed : A, X, Y
Program 10.4
Procedure title : PROCmulti_div
Variables required : first. second, totlen, count, hidiv_pointer, pointer, 1 buffer2, addr
Line numbers : 5150 to 5257
Length : 54 bytes
Zero page requirements : 11 bytes
Registers changed : A, X, Y
Program 10.5
Procedure title : PROConebyte_square
Variables required : byte, addr
Line numbers : 5270 to 5294
Length : 27 bytes
Zero page requirements : 2 bytes
Registers changed : A, X, Y
Program 10.6
Procedure title : PROCtwobyte_square
Variables required : byte, temp, addr
Line numbers : 5300 to 5332
Length : 39 bytes
Zero page requirements : 4 bytes
Registers changed : A, X, Y
Program 10.7
Procedure title : PROCtwo_byte_asl
Variables required : num, block, addr
Line numbers : 5350 to 5363
Length : 13 bytes
Zero page requirements : 2 bytes
Registers changed : A
Program 10.8
Procedure title : PROCtwo_byte_lsr
Variables required : num, block, addr
Line numbers : 5370 to 5383
Length : 13 bytes
Zero page requirements : 2 bytes
Registers changed : A
Program 10.9
Procedure title : PROCtwo_byte_ror
Variables required : num, block, addr
106 The BBC Micro Machine Code Portfolio
Line numbers : 5390 to 5403
Length : 13 bytes
Zero page requirements : 2 bytes
Registers changed : A
Program 10.10
Procedure title : PROCtwo_byte_rol
Variables required : num, block, addr
Line numbers : 5410 to 5423
Length : 13 bytes
Zero page requirements : 2 bytes
Registers changed : A
Program 10.11
Procedure title : PROCmulti_left
Variables required : start, bytes, addr
Line numbers : 5430 to 5446
Length : 16 bytes
Zero page requirements : 1 byte
Registers changed : A, X, Y
10 REM *** DO MODE *** 20 CLS 30 INPUT"Which MODE ?"M% 40 PROCmode (M%,&C00) 50 CALL mode 60 PRINT"This is MODE ";M% 70 END 80 : 6000 DEF PROCmode (action,addr) 6001 P%=addr 6002 [ 6003 .mode 6004 LDA #22 6005 JSR &FFEE 6006 LDA #action 6007 JSR &FFEE 6008 RTS 6009 ] 6010 ENDPROC
Program 11.1. PROCmode - performs a MODE change.
Program 11.2 provides a new screen mode. As it is made out of the MODE 2 screen I have christened it MODE 2A. This new mode still has all the sixteen colours of a normal MODE 2 available but only requires half the memory, 10K, for displaying them. The screen itself is composed of 25 rows of 20 characters. The program is given in its long-winded form so that I can try to explain its operation better! Obviously, it would be more economical in terms of memory to implement the final version with the VDU codes in a look-up table using an indexing routine to pull them out one by one and send them to OSWRCH.
10 REM *** NEW MODE 2A SCREEN *** 20 PROCmode2A (&A00) 30 CALL &A00 40 END 50 : 6100 DEF PROCmode2A (addr) 6101 FOR PASS=0 TO 3 STEP3 6102 P%=addr 6103 [ :OPT PASS 6104 LDA #22 6105 JSR &FFEE 6106 LDA #2 6107 JSR &FFEE 6108 LDA #23 6109 JSR &FFEE 6110 LDA #0 6111 JSR &FFEE 6112 LDA #6 6113 JSR &FFEE 6114 LDA #25 6115 JSR &FFEE 6116 JSR SIX 6117 LDA #23 6118 JSR &FFEE 6119 LDA #0 6120 JSR &FFEE 6121 LDA #7 6122 JSR &FFEE 6123 LDA #30 6124 JSR &FFEE 6125 JSR SIX 6126 LDA #23 6127 JSR &FFEE 6128 LDA #0 6129 JSR &FFEE 6130 LDA #12 6131 JSR &FFEE 6132 LDA #8 6133 JSR &FFEE 6134 JSR SIX 6135 LDA #23 6136 JSR &FFEE 6137 LDA #0 6138 JSR &FFEE 6139 LDA #14 6140 JSR &FFEE 6141 LDA #8 6142 JSR &FFEE 6143 JSR SIX 6144 LDA #5 6145 STA &FE40 6146 LDA #56 6147 STA &302 6148 LDA #24 6149 STA &309 6150 LDA #40 6151 STA &D9 6152 STA &34B 6153 STA &34E 6154 STA &351 6155 STA &354 6156 LDA #&40 6157 STA &7 6158 LDA #0 6159 STA &6 6160 RTS 6161 .SIX 6162 LDA #0 6163 LDX #6 6164 .AGAIN 6165 JSR &FFEE 6166 DEX 6167 BNE AGAIN 6168 RTS 6169 ] 6170 NEXT 6171 ENDPROC
Program 11.2. PROCmode2A - implements a scaled down version of MODE 2.
It might be easier to understand exactly what is going on if the assembler is broken down into its BASIC equivalent which, incidentally, will also produce the desired effect.
Line 6104 to 6107 | : MODE 2 |
Line 6108 to 6116 | : VDU 23;6,25;0;0;0; |
Line 6117 to 6125 | : VDU 23;7,30;0;0;0; |
Line 6126 to 6134 | : VDU 23;12,8;0;0;0; |
Line 6135 to 6143 | : VDU 23;14,8;0;0;0; |
Line 6144 to 6145 | : ?&FE40=5 |
Line 6146 to 6147 | : ?&302=56 |
Line 6148 to 6149 | : ?&309=24 |
Line 6150 to 6151 | : ?&D9=40 |
Line 6152 to 6155 | : ?&34B=40 |
: ?&34E=40 | |
: ?&351=40 | |
: ?&354=40 | |
Lines 6156 to 6159 | : HIMEM=&4000 |
(a) Program number of lines
(b) Set position of vertical sync in number of row times
(c) Set top of screen address
(d) Set cursor position
The remaining pokes write to the VDU variables directly which, strictly speaking, is rather naughty! The poke to &FE40 is writing to the system VIA scroll-controlling register, while the subsequent two pokes define the bottom row, in pixels, of the graphics window and the bottom row of the text window. &D9 holds the high byte of the current address of the top scan line of a character (HIMEM is being set to &4000, thus the &40); &34B high byte of the top cursor location; &34E top+1 address of user memory; &351 the high byte address of the top left-hand corner of the screen; and finally &354 the high byte of the screen memory size.
Because the screen is not an official mode it is organised rather crookedly. For example, the pixel coordinates for the Y axis do not run from 0 to 1023 as one might expect but from 225 to 1023. Also, the screen itself tends to sit in the middle of the TV rather than using it all. To counteract the Y axis distortion, the graphics origin could be reset to 0,225 using VDU 29, thus:
VDU 29,0;225;
MOVE 0,0
This will reduce the maximum on-screen Y graphics coordinate to 798 but the range 0 to 798 is easier to use than 225 to 1023. Figure 11.1 provides a suitable map of MODE 2A.
Fig. 11.1. The MODE 2A screen map.
Program 11.3 works along similar lines in that it pokes various VDU variables to set up a new graphics mode screen from MODE 5. However, rather than reprogramming the CRTC, it writes to the Video ULA using an OSBYTE call (lines 6027 to 6030). This writes, in fact, to the Video Control Register whose layout is given in Figure 11.2. The byte written is 224 or &E0 in hex, thus causing a large cursor two bytes in width to be displayed.
10 REM *** NEW MODE 5A *** 20 PROCmode5A (&A00) 30 DRAW1000,100 40 CALL &A00 50 MOVE 100,100 60 DRAW 1000,100 70 DRAW 1000,1000 80 DRAW 100,1000 90 DRAW 100,100 100 END 110 : 6020 DEF PROCmode5A (addr) 6021 P%=addr 6022 [ 6023 LDA #22 6024 JSR &FFEE 6025 LDA #5 6026 JSR &FFEE 6027 LDA #154 6028 LDX #224 6029 JSR &FFF4 6030 LDA #15 6031 STA &360 6032 LDA #1 6033 STA &361 6034 LDA #32 6035 STA &34F 6036 LDA #&55 6037 STA &363 6038 LDA #&AA 6039 STA &362 6040 LDA #9 6041 STA &30A 6042 LDA #20 6043 JSR &FFEE 6044 LDA #&54 6045 STA &07 6046 LDA #0 6047 STA &6 6048 JMP &FFEE 6049 ] 6050 ENDPROC
Program 11.3. PROCmode5A - implements a scaled down version of MODE 5.
Fig. 11.2. The Video Control Register
This mode requires just 10K of RAM but also allows 16 colours like MODE 2 and MODE 2A! The mode allows 16 rows of 10 characters and HIMEM is set to &5400. The program description follows.
Lines 6023 to 6026 : Select MODE 5.
Lines 6027 to 6029 : Write to video ULA cursor control bits.
Lines 6030 to 6031 : All 16 colours available.
Lines 6032 to 6033 : Two 4-bit pixels per byte.
Lines 6034 to 6035 : 32 bytes used per character.
Lines 6036 to 6039 : Set colour details.
Lines 6040 to 6041 : 10 characters on each line (0 to 9).
Lines 6042 to 6043 : Do VDU 20 and rest default colours.
Lines 6044 to 6048 : Set HIMEM = &5400.
10 REM *** DO MACHINE CODE MOVE *** 20 PROCmove(640,512,&A00) 30 MODE 5 40 CALL move 50 DRAW 640,512 60 END 70 : 6180 DEF PROCmove(xpos,ypos,addr) 6181 P%=addr 6182 [ 6183 .move 6184 LDA #25 6185 JSR &FFEE 6186 LDA #4 6187 JSR &FFEE 6188 LDA #xpos MOD 256 6189 JSR &FFEE 6190 LDA #xpos DIV 256 6191 JSR &FFEE 6192 LDA #ypos MOD 256 6193 JSR &FFEE 6194 LDA #ypos DIV 256 6195 JSR &FFEE 6196 RTS 6197 ] 6198 ENDPROC
Program 11.4. PROCmove - performs MOVE.
Program 11.5 uses the driver code 6 (line 6206) to execute the machine code equivalent of a DRAW. The positions passed into the procedure are taken to be the coordinates to draw to. The demo program draws a line diagonally across the MODE 4 screen from 0,0 to 1000,1000. Once again, immediate addressing is used in the program to obtain the X, Y coordinates which must therefore be passed into the procedure at assembly time.
10 REM *** DO MACHINE CODE DRAW LINE *** 20 PROCdraw(1000,1000,&C00) 30 MODE 4 40 MOVE 0,0 50 CALL &C00 60 END 70 : 6200 DEF PROCdraw(xcord,ycord,addr) 6201 P%=addr 6202 [ 6203 .draw_line 6204 LDA #25 6205 JSR &FFEE 6206 LDA #6 6207 JSR &FFEE 6208 LDA #xcord MOD 256 6209 JSR &FFEE 6210 LDA #xcord DIV 256 6211 JSR &FFEE 6212 LDA #ycord MOD 256 6213 JSR &FFEE 6214 LDA #ycord DIV 256 6215 JSR &FFEE 6216 RTS 6217 ] 6218 ENDPROC
Program 11.5. PROCdraw - performs DRAW.
10 REM *** DO MACHINE CODE PLOT *** 20 PROCplot(85,1000,1000,&C00) 30 MODE 4 40 MOVE 0,0 50 MOVE 1000,0 60 CALL plot 70 END 80 : 6220 DEF PROCplot(code,xcord,ycord,addr ) 6221 P%=addr 6222 [ OPT 2 6223 .plot 6224 LDA #25 6225 JSR &FFEE 6226 LDA #code 6227 JSR &FFEE 6228 LDA #xcord MOD 256 6229 JSR &FFEE 6230 LDA #xcord DIV 256 6231 JSR &FFEE 6232 LDA #ycord MOD 256 6233 JSR &FFEE 6234 LDA #ycord DIV 256 6235 JSR &FFEE 6236 RTS 6237 ] 6238 ENDPROC
Program 11.6. PROCplot - performs PLOT.
A PLOT is performed using the driver code which is equivalent to the plot function required. Program 11.6 shows how the PLOT code is passed into the procedure through the variable 'code' .The demo uses code 85 to draw and fill a triangle in a MODE 4 screen. As you may now realise, the previous two programs were, in fact, simply using the plot codes for move and draw.
Number | Colour | |
0 | Black | |
1 | Red | |
2 | Green | |
3 | Yellow | |
4 | Blue | |
5 | Magenta | |
6 | Cyan | |
7 | White | |
8 | Flashing black-white | |
9 | Flashing red-cyan | |
10 | Flashing green-magenta | |
11 | Flashing yellow-blue | |
12 | Flashing blue-yellow | |
13 | Flashing magenta-green | |
14 | Flashing cyan-red | |
15 | Flashing white-black | |
10 REM *** DO PRINT COLOUR *** 20 PROCcolour (1,&C00) 30 MODE 2 40 CALL colour 50 END 60 : 6250 DEF PROCcolour(print_colour,addr) 6251 P%=addr 6252 [ 6253 .colour 6254 LDA #17 6255 JSR &FFEE 6256 LDA #print_colour 6257 JSR &FFEE 6258 RTS 6259 ] 6260 ENDPROC
Program 11.7. PROCcolour - performs COLOUR.
Program 11.8 shows how the background colour can be redefined using VDU17 again. Essentially the program is the same as its predecessor. To stipulate a background colour, however, the most significant bit of the colour byte must be set. In everyday terms, this simply means adding 128 to the colour value. After passing the background colour to the VDU driver (lines 6276 to 6277) the screen must be cleared. This is facilitated simply by printing the equivalent of a VDU12 (lines 6278 to 6279). The demo program initialises a red MODE 2 screen.
10 REM *** DO BACKGROUND COLOUR *** 20 REM *** SET RED BACKGROUND *** 30 PROCbackgrnd (129,&C00) 40 MODE 2 50 CALL backgrnd 60 END 70 : 6270 DEF PROCbackgrnd (back_col,addr) 6271 P%=addr 6272 [ 6273 .backgrnd 6274 LDA #17 6275 JSR &FFEE 6276 LDA #back_col 6277 JSR &FFEE 6278 LDA #12 6279 JSR &FFEE 6280 RTS 6281 ] 6282 ENDPROC
Program 11.8. PROCbackgrnd - changes the mode background colour.
Performing GCOL is almost as easy, however. The GCOL statement requires two parameters. After issuing VDU18 first, the byte depicting the action required (i.e. AND, OR, EOR) should be passed to OSWRCH followed by the colour. These bytes are shown in Program 11.9 as 'action' and 'colour' and the associated demo program (lines 10 to 70) set up a flashing black and white diagonal line across the MODE 2 screen.
The graphics screen can be cleared from BASIC using the command CLG. In machine code this is simplicity it selfand only requires the vdu driver to print the code 16 through OSWRCH. Program 11.10 demonstrates this.
10 REM *** DO MACHINE CODE GCOL *** 20 REM *** FLASHING B/W LINE *** 30 PROCgcol (0,8,&C00) 40 MODE 2 50 CALL gcol 60 MOVE 0,0:DRAW 1000,1000 70 END 80 : 6285 DEF PROCgcol (action,colour,addr) 6286 P%=addr 6287 [ 6288 .gcol 6289 LDA #18 6290 JSR &FFEE 6291 LDA #action 6292 JSR &FFEE 6293 LDA #colour 6294 JSR &FFEE 6295 RTS 6296 ] 6297 ENDPROC
Program 11.9. PROCgcol - performs GCOL.
10 REM**CLEAR GRAPHICS SCREEN -CLG** 20 PROCclg (&C00) 30 MODE 2 40 COLOUR129 50 CLS 60 PRINT"PRESS A KEY TO CLEAR SCREEN" 70 A=GET 80 CALL &C00 90 END 100 : 6300 DEF PROCclg (addr) 6301 P%=addr 6302 [ 6303 .clear_graphics 6304 LDA #16 6305 JSR &FFEE 6306 RTS 6307 ] 6308 ENDPROC
Program 11.10. PROCclg - performs CLG.
Programming the palette is done as in BASIC using VDU 19 in the
form:
VDU 19, log, phy, 0,0,0
where 'log' and 'phy' refer to the logical and physical colours respectively. Program 11.11 shows how this is translated into assembler. After the 19 is printed (lines 6314 and 6315) the logical and physical colour codes are passed to OSWRCH (lines 6316 to 6319) followed by the three padding zeros (lines 6320 to 6323) reserved for future expansion, whatever that is! Once again, the values passed into the procedure for 'log' and 'phy' are interpreted as immediate values by the assembler.
The lines 10 to 110 show how the procedure is used in this case to re-set the current screen background logical colour to each physical colour in turn.
10 REM *** DO VDU 19 *** 20 REM** GO FRU ALL COLOURS ** 30 MODE 2 40 FOR loop=1 TO 15 50 PROCchange_palette (0,loop,&C00) 60 CALL chgpalette 70 FOR N=0 TO 999:NEXT 80 NEXT 90 PROCchange_palette (0,0,&C00) 100 CALL chgpalette 110 END 120 : 6310 DEF PROCchange_palette (log,phy,ad dr) 6311 P%=addr 6312 [ OPT 2 6313 .chgpalette 6314 LDA #19 6315 JSR &FFEE 6316 LDA #log 6317 JSR &FFEE 6318 LDA #phy 6319 JSR &FFEE 6320 LDA #0 6321 JSR &FFEE 6322 JSR &FFEE 6323 JSR &FFEE 6324 RTS 6325 ] 6326 ENDPROC
Program 11.11. PROCchange_palette - reprograms the palette using
OSWORD.
XY+0 | : previous X coordinate LSB | |
XY+1 | : previous X coordinate MSB | |
XY+2 | : previous Y coordinate LSB | |
XY+3 | : previous Y coordinate MSB | |
XY+4 | : current X coordinate LSB | |
XY+5 | : current X coordinate MSB | |
XY+6 | : current Y coordinate LSB | |
XY+7 | : current Y coordinate MSB | |
10 REM *** READ LAST 2 GRAPHICS *** 20 REM *** CURSOR POSITIONS *** 30 MODE 4 40 MOVE 200,200 50 DRAW 500,500 60 MOVE 700,700 70 DRAW 900,900 80 CLS 90 PROCgcursor(&70,&C00) 100 CALL &C00 110 FOR loop=&70 TO &77 120 PRINT~loop;" ";?loop 130 NEXT loop 140 END 150 : 6330 DEF PROCgcursor(block,addr%) 6331 FOR pass=0 TO 3 STEP3 6332 P%=addr% 6333 [ OPT pass 6334 LDA #13 6335 LDX #block MOD 256 6336 LDY #block DIV 256 6337 JSR &FFF1 6338 RTS 6339 ] 6340 NEXT 6341 ENDPROC
Program 11.12. PROCgcursor - uses OSWORD to read the last two
graphics coordinates.
The condition of any pixel on the screen can also be read using OSWORD with the accumulator holding 9 - in effect, mimicking BASIC's POINT command (see Program 11.13). Before calling the operating system routine, the obligatory parameter block (detailed in Figure 11.5) must have some relevant details placed into it, namely the X,Y coordinates of the byte to be tested. Each coordinate uses two bytes ofthe parameter block and these are derived in lines 6345 to 6361 ofthe procedure. The procedure again assumes that the actual coordinates, and not an address containing them, are passed through the variable X and Y. Each byte is then stored in the relevant parameter block location. After seeding the parameter block address into the index registers (lines 6362 to 6364) thc OSWORD call is performed leaving the logical colour of the craordinate in the fifth block of the parameter block - or &FF if the print was off of the screen.
10 REM READ PIXEL VALUES 20 PROCpixel(&70,100,100,&C00) 30 MODE 2 40 CALL&C00 50 PRINT~block?4 60 END 70 : 6350 DEF PROCpixel(block,X,Y,addr) 6351 FOR pass=0 TO 3 STEP3 6352 P%=addr 6353 [ OPT pass 6354 LDA #X MOD 256 6355 STA block 6356 LDA #X DIV 256 6357 STA block+1 6358 LDA #Y MOD 256 6359 STA block+2 6360 LDA #Y DIV 256 6361 STA block+3 6362 LDX #block MOD 256 6363 LDY #block DIV 256 6364 LDA #9 6365 JSR &FFF1 6366 RTS 6367 ] 6368 NEXT 6369 ENDPROC 6332 P%=addr% 6333 [ OPT pass 6334 LDA #13 6335 LDX #block MOD 256 6336 LDY #block DIV 256 6337 JSR &FFF1 6338 RTS 6339 ] 6340 NEXT 6341 ENDPROC
Program 11.13. PROCpixel - reads the state of a screen pixel.
XY+0 | : | X coordinate LSB |
XY+1 | : | X coordinate MSB |
XY+2 | : | Y coordinate LSB | |
XY+3 | : | Y coordinate MSB |
XY+4 | : | Logical colour of point, &FF if point off screen. | |
10 REM *** READ COLOUR PALETTE *** 20 MODE 4 30 VDU19,1,3,0,0,0 40 PROCreadpalette (&70,&C00,1) 50 CALL &C00 60 PRINT"Logical colour :";?&70 70 PRINT"Physical colour :";?&71 80 END 90 : 6380 DEF PROCreadpalette (block, addr%, L%) 6381 FOR pass=0 TO 3 STEP 3 6382 P%=addr% 6383 [ OPT pass 6384 LDA #L% 6385 STA block 6386 LDA #11 6387 LDX #block MOD 256 6388 LDY #block DIV 256 6389 JSR &FFF1 6390 RTS 6391 ] 6392 NEXT pass 6393 ENDPROC
Program 11.14. PROCreadpalette - reads the physical colour associated with a logical colour.
The colour palette can itself be read using an OSWORD 11 as shown in Program 11.14. The logical colour to be read should be placed into the five-byte parameter block. After the call, the physical colour currently assigned to the logical colour is in the second byte of the parameter block. The remaining three parameter block bytes contain zero - yes, for future expansion! The BASIC demo uses the call to read the physical colour assigned to logical colour 1 on the MODE 4 screen, this having been defined prior to the call in line 30 as 3.
Program 11.15 performs the operation in the reverse direction by writing to the palette using OSWORD 12. The parameter block is identical to that in a read operation except that the physical colour to be written must also be placed into the parameter block. The procedure passes both logical and physical colours to the assembler through the variables L% and PY%. The demo resets the MODE 4 logical colour 0, the background colour, to physical colour yellow, thereby performing an instant change in background colour.
10 REM *** WRITE TO PALETTE *** 20 MODE 4 30 PROCwritepalette (&70,0,3,&C00) 40 CALL &C00 50 END 60 : 6400 DEF PROCwritepalette (block,L%,PY% ,addr%) 6401 P%=addr% 6402 [ 6403 LDA # L% 6404 STA block 6405 LDA # PY% 6406 STA block+1 6407 LDA #0 6408 STA block+2 6409 STA block+3 6410 STA block+4 6411 LDA #12 6412 LDX #block MOD 256 6413 LDY #block DIV 256 6414 JSR &FFF1 6415 RTS 6416 ] 6417 ENDPROC
Program 11.15. PROCwritepalette - performs VDU 19.
10 REM ** CONVERT X,Y TO ADDRESS ** 20 PROCxyaddr(&80, &72, &71,&900) 30 REPEAT 40 INPUT"What is the X axis value - 0 to 79 "?&71 50 INPUT''"What is the Y axis value - 0 to 255 "?&72 60 CALLCODE 70 PRINT~?&80+?&81*256 80 UNTIL0 90 : 6500 DEFPROCxyaddr(vector, yaxis, xaxis , addr) 6501 FORI%=0TO2 STEP2 6502 P%=addr 6503 [ OPTI% 6504 .CODE 6505 LDA#0 6506 STA vector 6507 STA vector+2 6508 LDA#&30 6509 STA vector+1 6510 LDA yaxis 6511 AND#7 6512 STA yaxis +1 6513 EOR yaxis 6514 LSRA 6515 LSRA 6516 TAY 6517 INY 6518 LDA&C375,Y 6519 CLC 6520 ADC vector 6521 ADC yaxis +1 6522 STA vector 6523 DEY 6524 LDA&C375,Y 6525 ADC vector+1 6526 STA vector+1 6527 LDA xaxis 6528 LDX#3 6529 .LOOP 6530 ASLA 6531 ROL vector+2 6532 DEX 6533 BNE LOOP 6534 ADC vector 6535 STA vector 6536 LDA#0 6537 ADC vector+2 6538 ADC vector+1 6539 STA vector+1 6540 LDY#0 6541 LDA#&FF 6542 STA(vector),Y 6543 RTS 6544 ] 6545 NEXT 6546 ENDPROC
Program 11.16. PROCxy_addr - converts an X,Y coordinate into a screen address.
The program begins by clearing a few bytes of memory (lines 6505 to 6509) and setting vector to the start screen address. The MOD 8 value of the 'yaxis' is then calculated along with the DIV 8 value (lines 6510 to 6513). The actual value to be calculated is. in fact, Y DIV 8 *640. However, since the table values are two-byte the DIV is restricted to 4 (lines 6514 to 6516). The accumulator is transferred into the Y register to get the index into the table, and is subsequently incremented to get the second, low byte (lines 6516 to 6518). The low byte is added to give Y axis MOD 8 (lines 6519 to 6522) and after extracting the high byte from the table this is added to give Y axis DIV 8 *640 (lines 6523 to 6527). Finally, the X axis value is multiplied by 8 and any bits falling off are caught in 'vector+3' (lines 6528 to 6533). This is then added to the low byte of the screen address to give the final address (lines 6534 to 6539). By way of demonstration, &F is then poked into screen memory at this point: to see this the program will need to be run in MODE 2 (lines 6540 to 6542).
Program 11.2
Procedure title : PROCmode2A
Variables required : addr
Line numbers : 6100 to 6170
Length : 153 bytes
Zero page requirements : none
Registers changed : A, X
Program 11.3
Procedure title : PROCmode5A
Variables required : addr
Line numbers : 6020 to 6049
Length : 58 bytes
Zero page requirements : none
Registers changed : A
Program 11.4
Procedure title : PROCmove
Variables required : xpos, ypos, addr
Line numbers : 6180 to 6198
Length : 31 bytes
Zero page requirements : none
Registers changed : A
Program 11.5
Procedure title : PROCdraw
Variables required : xcord, ycord, addr
Line numbers : 6200 to 6218
Length : 31 bytes
Zero page requirements : none
Registers changed : A
Program 11.6
Procedure title : PROCplot
Variables required : code, xcord, ycord, addr
Line numbers : 6220 to 6238
Length : 31 bytes
Zero page requirements : none
Registers changed : A
Program 11.7
Procedure title : PROCcolour
Variables required : print_colour, addr
Line numbers : 6250 to 6260
Length : 11 bytes
Zero page requirements : none
Registers changed : A
Program 11.8
Procedure title : PROCbackgrnd
Variables required : back_col, addr
Line numbers : 6270 to 6282
Length : 16 bytes
Zero page requirements : none
Registers changed : A
Program 11.9
Procedure title : PROCgcol
Variables required : action, colour, addr
Line numbers : 6285 to 6297
Length : 16 bytes
Zero page requirements : none
Registers changed : A
Program 11.10
Procedure title : PROCclg
Variables required : addr
Line numbers : 6300 to 6308
Length : 6 bytes
Zero page requirements : norie
Registers changed : A
Program 11.11
Procedure title : PROCchange_palette
Variables required : log, phy, addr
Line numbers : 6310 to 6326
Length : 27 bvtes
Zero page requirements : none
Registers changed : A
Program 11.12
Procedure title : PROCgcursor
Variables required : block, addr
Line numbers : 6330 to 6341
Length : 10 bytes
Zero pagc requirements : none
Registers changed : A, X, Y
Program 11.13
Procedure title : PROCpixel
Variables required : block, X, Y, addr
Line numbers : 6350 to 6369
Length : 26 bytcs
Zero page requirements : none
Registers changed : A, X, Y
Program 11.14
Procedure title : PROCreadpalette
Variables required : block, addr, L%
Line numbers : 6380 to 6393
Length : 14 bytes
Zero pagc requirements : none
Registers changed : A, X, Y
Program 11.15
Procedure title : PROCwritepalette
Variables required : block, L%, PY%, addr
Line numbers : 6400 to 6417
Length : 26 bytes
Zero page requirements : none
Registers changed : A, X, Y
Vision On129
Program 11.16
Procedure title : PROCxy_addr
Variables required : vector, yaxis, xaxis, addr
Line numbers : 6500 to 6545
Length : 69 bytes
Zero page requirements : 6 bytes
Registers changed : A, X, Y
Program 12.1: Byte search.
Program 12.2: Add a byte to an ordered list.
Program 12.3: Delete a byte from an ordered list.
Program 12.4: Find minimum and maximum values in an unordered list.
Program 12.5: Delete a byte from an unordered list.
Program 12.6: Access a byte in a one-dimensional byte array.
Program 12.7: Access a byte in a two-dimensional byte array.
Program 12.8: Access a word from a one-dimensional word array.
Program 12.9: Four-byte signed integer sort.
Program 12.10: Form new list from an old list of every nth element.
Program 12.11: Perform quicksort on a fbur-byte integer array.
1,2,3,4,5,6. . .
would be an example of an ordered list, whereas
4,9,2,6,8,12,34,2,1,0. . .
is an example of an unordered list.
Because the list is ordered, it is not necessary for the machine code to search through the entire list. What the binary search technique does is to divide the list into half, calculate which half the search byte is in and divide this section in half again. This process continues until the search byte is located by zeroing in on it.
10 REM *** SINGLE BYTE BINARY SEARCH *** 20 PROCbin_search (&70,&71,&73,&74,&A 00) 30 FOR loop=0 TO 150 40 loop?&4001=loop 50 NEXT loop 60 ?&4000=150 70 !&71=&4000 80 ?&70=75 100 CALL bin_search 110 RESULT%=?&73 120 IF RESULT%=0 PRINT"NOT FOUND" : EN D 130 PRINT"BYTE LOCATED AT +";RESULT% 140 end 150 : 7000 DEF PROCbin_search (byte,list,pos, temp,addr) 7001 FOR PASS=0 TO 3 STEP 3 7002 P%=addr 7003 [ 7004 OPT PASS 7005 .bin_search 7006 LDY #0 7007 LDA (list),Y 7008 STA pos 7009 STA temp 7010 INY 7011 .next_byte 7012 LSR pos 7013 BNE not_finished 7014 BCS over 7015 RTS 7016 .not_finished 7017 BCC over 7018 INC pos 7019 .over 7020 LDA (list),Y 7021 CMP byte 7022 BEQ byte_found 7023 BCS sub_inc 7024 TYA 7025 ADC pos 7026 CMP temp 7027 BEQ equal 7028 BCS next_byte 7029 .equal 7030 TAY 7031 JMP next_byte 7032 .sub_inc 7033 TYA 7034 SBC pos 7035 BEQ next_byte 7036 BCS set 7037 BMI next_byte 7038 .set 7039 TAY 7040 JMP next_byte 7041 .byte_found 7042 STY pos 7043 RTS 7044 ] 7045 NEXT 7046 ENDPROC
Program 12.1. PROCbin_search - performs a binary search on an ordered list.
The program searches the list looking for the 8-bit value held in 'byte'. The list is addressed indirectly so the vector 'list' is used to hold its address, &4000 in the demo. Note that the very first byte of the list is not, in fact, an element but the length of the list itself. The list proper therefore starts at (list)+1. The variable 'pos' is used to return the position of the element in the list; if this byte contains 0 it means that the element was not found. Remember that a value of 1 would be returned if the element was the very first in the list.
The binary search begins by obtaining the length of the list from the length of list element (lines 7005 to 7009). The search proper is then begun by executing a logical shift right on the list length byte in 'pos' (line 7012), thus dividing it by two. A result of zero indicates that the list does not contain the element being searched for and the RTS of line 7015 returns back to the calling routine leaving 'pos' holding zero. If the carry flag is set, control continues from 'over' (line 7019). The INC instruction of line 7018 is used to round any odd numbers up to an even one should the division have left an odd value in 'pos'.
The byte comparison is nothing unusual. If the byte is found, the branch to 'byte_found' is performed (line 7022) where the Y register's contents art; placed in 'pos' and an RTS performed (lines 7041 to 7043). If the byte is not located then the program needs to determine which half of the section in which it is located contains the byte so that the program can halve that section. Assuming that the byte is larger than the element tested, the branch to 'sub_inc' is performed (line 7023). Here the current 'pos' is subtracted from the Y register, now transferred into the accumulator (lines 7032 to 7034) resulting in the lower portion of the list half being searched for the 'byte'. If, on the other hand, the byte is less than the element tested the branch does not take place and the 'pos' is added to the Y register so that the search continues in the upper section of the list half (lines 7024 to 7028).
The demo section of the program (lines 30 to 130) shows how the data needs to be set up before calling the subroutine. The procedural call assembles the routine at &A00 using five locations in zero page for variable storage, though only 'list' need be there. The FOR. . .NEXT loop then pokes an ordered list into memory from &4000 placing the number of elements in the list, 150, into the first byte (lines 30 to 60), before placing the address ofthe list in 'list' (line 70). The byte to be searched for - in this case 75 - is then poked into location &70 as this corresponds to 'byte'. After running the program, the result returned is
BYTE LOCATED AT +76
which is correct because 75+1=76.
Fig. 12.1. Flowchart for PROCordered_add.
10 REM *** ADDITION OF AN ELEMENT TO *** 20 REM *** AN ORDERED LIST *** 30 PROCbin_search (&70,&71,&73,&74,&3 000) 40 addr=P% 50 PROCordered_add(&70,&71,&73,&74,ad dr) 60 FOR loop=0 TO 254 STEP2 70 ?(&4000+(loop/2))=loop 80 NEXT 90 ?&4000=127 100 !&71=&4000 110 ?&70=221 120 FOR N=&4000 TO (&4000+128) 130 PRINT ~N;" ";?N 140 NEXT 150 PRINT"Press a key to execute " 160 A=GET 170 CALL add_element 180 FOR N=&4000 TO (&4000+128) 190 PRINT ~N;" ";?N 200 NEXT 210 END 220 : 7100 DEF PROCbin_search (byte,list,pos, temp,addr) 7101 FOR PASS=0 TO 3 STEP 3 7102 P%=addr 7103 [ 7104 OPT PASS 7105 .bin_search 7106 LDY #0 7107 LDA (list),Y 7108 STA pos 7109 STA temp 7110 INY 7111 .next_byte 7112 LSR pos 7113 BNE not_finished 7114 BCS over 7115 RTS 7116 .not_finished 7117 BCC over 7118 INC pos 7119 .over 7120 LDA (list),Y 7121 CMP byte 7122 BEQ byte_found 7123 BCS sub_inc 7124 TYA 7125 ADC pos 7126 CMP temp 7127 BEQ equal 7128 BCS next_byte 7129 .equal 7130 TAY 7131 JMP next_byte 7132 .sub_inc 7133 TYA 7134 SBC pos 7135 BEQ next_byte 7136 BCS set 7137 BMI next_byte 7138 .set 7139 TAY 7140 JMP next_byte 7141 .byte_found 7142 STY pos 7143 RTS 7144 ] 7145 NEXT 7146 X=addr 7147 ENDPROC 7148 : 7149 DEF PROCordered_add(byte,list,pos, temp,addr) 7150 FOR PASS=0 TO 3 STEP 3 7151 P%=addr 7152 [ 7153 OPT PASS 7154 .add_element 7155 JSR bin_search 7156 LDA pos 7157 BNE present 7158 STY pos 7159 SEC 7160 LDA temp 7161 SBC pos 7162 TAX 7163 LDA byte 7164 CMP (list),Y 7165 BCS greater 7166 INY : INX 7167 JMP get_index 7168 .greater 7169 INC pos 7170 CPX #0 7171 BEQ enter_element 7172 .get_index 7173 LDY temp 7174 .next_element 7175 LDA (list),Y 7176 INY 7177 STA (list),Y 7178 DEY 7179 DEY 7180 DEX 7181 BNE next_element 7182 .enter_element 7183 LDA byte 7184 LDY pos 7185 STA (list),Y 7186 INC temp 7187 LDA temp 7188 LDY #0 7189 STA (list),Y 7190 .present 7191 RTS 7192 ] 7193 NEXT 7194 ENDPROC
Program 12.2. PROCordered_add - adds a value to an ordered list.
The PROCordered_add procedure (lines 7149 to 7194) uses the same variables/locations as the binary search procedure. Before it is called, the machine code assembled by the former expects to find the element to be added to the ordered list in 'byte' ,the location ofthe list in 'list' and the first byte ofthe list stating the number of elements in the list.
After the 'bin_search' subroutine call, the accumulator' scontents are loaded with 'pos'. Remember, if the list did not contain the byte being searched out, this will be zero. Ifthe byte is non-zero, the list already contains the element and need not be added - therefore the branch to 'present' (line 7157) is performed and the program completes. If the byte is zero the byte is not present, and the Y register holds the position of the last element to be examined before the search was exited. As it happens, this also corresponds to the position in the list where the binary search routine expected to find it! The Y register is therefore saved in 'pos' (line 7158) and the position whcrc the byte to be added is to one side of this element.
Before the routine locates exactly where the byte is to be added it must first calculate how many elements must be moved up a byte to make space for the new addition. This is really quite simple as it just requires 'pos' to be subtracted from 'temp' (lines 7159 to 7161), where 'temp' is used to hold the list's length. The result is then transferred across into the X register (line 7162) to act as a loop counter when the move takes place.
Calculating the exact position of the byte in the list is facilitated with a simple comparison with the element pointed to by the Y register index (line 7164). If this sets the carry flag, the position is immediately following the element pointed to by the index register Therefore the branch to 'greater' is performed (line 7165) where 'pos' is incremented. The compare X with zero instructions (lines 7170 to 7171) test to see whether the entry position will be outside the list in which case no space needs to be made for it. If the comparison clears the carry flag then after incrementing the X register (line 7166) a jump is performed.
Moving the upper section of the list is straightforward. Starting with the highest element, each byte is read, the Y register incremented and the byte stored (lines 7172 to 7177). Subtracting two from the Y register restores the index at the next byte, while the X register acting as counter is decremented to signify one less element to move (lines 7178 to 7181). Ensuring that the move is performed in the reverse order, down memory, is important so as not to overwrite other elements in the list before they are also transferred!
Finally, the 'enter_element' routine pokes the new element into its correct position and the number of elements in the list is updated by adding one to it (lines 7182 to 7189).
The BASIC demo provides details on the ordered add routine's use. The two sections of assembler are assembled (lines 30 to 50) passing the value of the program counter through 'addr' to ensure that the two subroutines occupy successive bytes in memory. The FOR...NEXT loop then pokes an ordered list into memory from &4000 which consists of only even numbers (lines 60 to 80). Lines 90 and 100 set up the number of elements in the list and the 'list' vector itself. The value to be inserted into the list. 221 - an odd number - is then poked into 'byte'.
The entire contents of the list are then printed out to ensure that only even numbers in steps of two are present (lines120 to160). After the 'add _element' call the list is reprinted and the new element can be seen at the top of the screen (lines 170 to 210).
10 REM *** DELETE AN ENTRY FROM WITHI N *** 20 REM *** AN ORDERED LIST *** 30 PROCbin_search (&70,&71,&73,&74,&3 000) 40 addr=P% 50 PROCordered_del(&70,&71,&73,&74,ad dr) 60 FOR N%=0 TO 200 70 ?(&4001+N%)=N% 80 NEXT 90 ?&70=179 100 ?&4000=200 110 !&71=&4000 120 CALL del_element 130 FOR N%=0 TO 200 140 PRINT~(N%+&4001);" ";?(N%+&4001) 150 NEXT 160 END 170 : 7200 DEF PROCbin_search (byte,list,pos, temp,addr) 7201 FOR PASS=0 TO 3 STEP 3 7202 P%=addr 7203 [ 7204 OPT PASS 7205 .bin_search 7206 LDY #0 7207 LDA (list),Y 7208 STA pos 7209 STA temp 7210 INY 7211 .next_byte 7212 LSR pos 7213 BNE not_finished 7214 BCS over 7215 RTS 7216 .not_finished 7217 BCC over 7218 INC pos 7219 .over 7220 LDA (list),Y 7221 CMP byte 7222 BEQ byte_found 7223 BCS sub_inc 7224 TYA 7225 ADC pos 7226 CMP temp 7227 BEQ equal 7228 BCS next_byte 7229 .equal 7230 TAY 7231 JMP next_byte 7232 .sub_inc 7233 TYA 7234 SBC pos 7235 BEQ next_byte 7236 BCS set 7237 BMI next_byte 7238 .set 7239 TAY 7240 JMP next_byte 7241 .byte_found 7242 STY pos 7243 RTS 7244 ] 7245 NEXT 7246 X=addr 7247 ENDPROC 7248 : 7249 DEF PROCordered_del(byte,list,pos, temp,addr) 7250 FOR PASS=0 TO 3 STEP 3 7251 P%=addr 7252 [ 7253 OPT PASS 7254 .del_element 7255 JSR bin_search 7256 LDA pos 7257 BEQ all_done 7258 INY 7259 .next_element 7260 LDA (list),Y 7261 DEY 7262 STA (list),Y 7263 INY 7264 INY 7265 CPY temp 7266 BCC next_element 7267 BEQ next_element 7268 LDA temp 7269 SBC #1 7270 LDY #0 7271 STA (list),Y 7272 .all_done 7273 RTS 7274 ] 7275 NEXT 7276 ENDPROC
Program 12.3. PROCordered_del - deletes a value from an ordered list.
10 REM *** FIND MINIMUM AND MAXIMUM * ** 20 REM *** VALUES IN AN UNORDERED LIS T *** 30 PROCmax_min_list(&70,&71,&72,&C00) 40 !&72=&4000 50 FOR loop=1 TO 100 60 loop?&4000=RND(255) 70 NEXT 80 ?&4000=100 90 CALL minmax 100 PRINT "Minimum value was :";?&70 110 PRINT "Maximum value was :";?&71 120 END 130 : 7300 DEF PROCmax_min_list(min,max,list, addr) 7301 FOR PASS=0 TO 3 STEP 3 7302 P%=addr 7303 [ OPT PASS 7304 .minmax 7305 LDY #0 7306 LDA (list),Y 7307 TAX 7308 INY 7309 LDA (list),Y 7310 STA min 7311 STA max 7312 .next_byte 7313 DEX 7314 BEQ all_done 7315 INY 7316 LDA (list),Y 7317 CMP min 7318 BCS test_max 7319 STA min 7320 .test_max 7321 CMP max 7322 BCC next_byte 7323 BEQ next_byte 7324 STA max 7325 JMP next_byte 7326 .all_done 7327 RTS 7328 ] 7329 NEXT 7330 ENDPROC
Program 12.4. PROCmax_min_list - finds the maximum and minimum values in an unordered list.
The routine finds these values by taking the first element from the list and then using this initially as the maximum and minimum values. Then each of the remaining elements in the list are compared in turn. If an element is found to be larger than the present maximum value it becomes the new maximum value. Similarly, if an element is located that is smaller than the current minimum value it takes the current minimum value's place. When the last element in the list has been sampled, the maximum and minimum values have been located.
The 'minmax' routine performs this get and compare procedure. The address of the unordered list is held within the vector 'list' while the variables 'min' and 'max' are used as stores for the two extremes. As with the previous list operations, the first byte in the list holds its length. The subroutine begins by accessing this length byte and moving it across into the X register to act as a counter after which the first element is read and placed in the two variable stores (lines 7304 to 7311).
The main loop of the program is entered at line 7312. The X register counter is decremented, and if zero all the elements have been shifted through so the program exits (lines 7313 to 7314). The indexing register is incremented and the next element in the list sought (lines 7315 to 7316). The element in the accumulator is then tested against that in 'min'. If this clears the carry flag a smaller element is indicated so this is stored as the new minimum value. If the carry is set by the comparison a larger value than 'min' is indicated so a branch to 'test_max' is performed (lines 7318 to 7320). Here, a comparison against 'max' takes place. If the byte in the accumulator is found to be greater, it is stored at 'max' ,otherwise the next element in the list is sought out (lines 7321 to 7325).
The BASIC primer pokes 100 random single-byte values into a list starting at &4000 (lines 50 to 70) and the maximum and minimum values are ascertained by the 'minmax' routine.
10 REM *** DELETE ITEM FROM UNORDERED LIST *** 20 PROCunordered_del(&70,&71,&C00) 30 ?&70=255 40 !&71=&4000 50 FOR N=1 TO 100 60 ?(&4000+N)=N 70 ?&4000=100 80 NEXT 90 ?&4050=255 100 FOR N=&4000 TO &4064 110 PRINT~N;" ";?N 120 NEXT 130 A=GET 140 CALL &C00 150 FOR N=&4000 TO &4064 160 PRINT~N;" ";?N 170 NEXT 180 END 190 : 7400 DEF PROCunordered_del(byte,list,ad dr) 7401 FOR PASS=0 TO 3 STEP3 7402 P%=addr 7403 [ OPT PASS 7404 LDY #0 7405 LDA (list),Y 7406 TAX 7407 LDA byte 7408 .next 7409 INY 7410 CMP (list),Y 7411 BEQ delete 7412 DEX 7413 BNE next 7414 RTS 7415 .delete 7416 DEX 7417 BEQ updat 7418 INY 7419 LDA (list),Y 7420 DEY 7421 STA (list),Y 7422 INY 7423 JMP delete 7424 .updat 7425 LDA (list,X) 7426 SBC #1 7427 SYA (list,X) 7428 RTS 7429 ] 7430 NEXT 7431 ENDPROC
Program 12.5. PROCunordered_del - deletes a byte from an unordered list.
As with the other list processing programs, the address of the list is held in the vector 'list' while the first element in the list itself is its length. The byte to be deleted is placed in 'byte' prior to the call. Note that only the first occurrence of the 'byte' is deleted, not all occurrences.
The BASIC program sets up an unordered list (well, it's actually ordered but who cares!) and then pokes the value 1155 into location &4050 (now its unordered!). The list is displayed both prior to and after the machine code call to show that the e]ement has indeed been deleted from the list (lines 30 to 170).
10 REM **GET BYTE FROM BYTE ARRAY** 20 PROCbyte_array (&70,&71,&C00) 30 FOR array=0 TO 255 40 ?(array+&4000)=array 50 NEXT 60 !&71=&4000 70 ?&70=100 80 CALL byte_array 90 PRINT"Element in array was:";?&70 100 END 110 : 7500 DEF PROCbyte_array (subscript,arra y,addr) 7501 FOR pass=0 TO 3 STEP 3 7502 P%=addr 7503 [ 7504 OPT pass 7505 .byte_array 7506 LDA subscript 7507 CLC 7508 ADC array 7509 STA array 7510 BCC over 7511 INC array+1 7512 .over 7513 LDY #0 7514 LDA (array),Y 7515 STA subscript 7516 RTS 7517 ] 7518 NEXT 7519 ENDPROC
Program 12.6. PROCbyte_array - extracts a value from a one-dimensional byte array.
Accessing a byte from a two-dimensional byte array is a little less straightforward as there are two subscripts to take into consideration. These two subscripts are the row length and the column length. The resultant program is listed as Program 12.7 and basically it works by multiplying the row subscript by the row size and then adding the column subscript to its result. This result is then added to the base. address of the array to give an absolute address.
10 REM *** ACCESSING A TWO DIMENSIONA L *** 20 REM *** BYTE ARRAY ANYWHERE IN RAM *** 30 PROCtwodim_byte(&70,&72,&74,&76,&7 8,&3000) 40 FOR count=1 TO 32 50 ?(count+&4000)=count 60 NEXT 70 !&70=2 80 !&72=4 90 !&74=8 100 !&78=&4000 110 CALL twodimbyte 120 @%=0 130 PRINT''' 140 PRINT"Address of element in array: &"; 150 PRINT~!&78 AND &FFFF 160 PRINT''"Byte located here :"; 170 PRINT?&70 180 END 190 : 7530 DEF PROCtwodim_byte (subscript1,su bscript2,sub_size,temp,array,addr) 7531 FOR PASS=0 TO 3 STEP 3 7532 P%=addr 7533 [ 7534 OPT PASS 7535 .twodimbyte 7536 LDA #0 7537 STA temp 7538 STA temp+1 7539 LDX #17 7540 CLC 7541 .multiply 7542 ROR temp+1 7543 ROR temp 7544 ROR subscript1+1 7545 ROR subscript1 7546 BCC no_add 7547 CLC 7548 LDA sub_size 7549 ADC temp 7550 STA temp 7551 LDA sub_size+1 7552 ADC temp+1 7553 STA temp+1 7554 .no_add 7555 DEX 7556 BNE multiply 7557 LDA subscript1 7558 CLC 7559 ADC subscript2 7560 STA subscript1 7561 LDA subscript1+1 7562 ADC subscript2+1 7563 STA subscript1+1 7564 LDA array 7565 CLC 7566 ADC subscript1 7567 STA array 7568 LDA array+1 7569 ADC subscript1+1 7570 STA array+1 7571 LDY #1 7572 LDA (array),Y 7573 STA subscript1 7574 RTS 7575 ] 7576 NEXT 7577 ENDPROC
Program 12.7. PROCtwodim_byte - extracts a value from a two-dimensional byte array.
The program commences by clearing two bytes at 'temp' which will act as a partial product during the multiplication procedure which is based on a standard shift and add type of affair. The X register is then initialised as a counter, to count out the shifts required during the multiplication (lines 7536 to 7539). The 'multiply' routine (lines 7541 to 7556) then performs the row subscript) (row length multiplication. When this is completed, the second subscript, the column, is added to the product of the multiplication (lines 7558 to 7563) and then to the base address of the array itself (lines 7565 to 7570). Finally, the array element is loaded into the accumulator and placed at 'subscript1'.
The BASIC program sets up the two-dimensional byte array using concurrent numbers. Of course, the array is stored physically in memory as a continuous list but is implemented as depicted in Figure 12.2, consisting of 4 rows of 8 columns. The position of any point in the array is given by (row,column), therefore the byte at (2,5) would be 22. The two-byte subscripts are poked into their two-byte locations at 'subscript1' and 'subscript2' (lines 70 and 80). Line 90 then places the row length into the two-byte variable 'sub_size'. After calling the machine code, the absolute address of the element is extracted from 'array'and the byte that is located there is printed (lines 110 to 170).
Fig. 12.2. Construction of a two-dimensional byte array.
10 REM *** GET WORD FROM SINGLE WORD ARRAY *** 20 PROCword_array(&70,&72,&C00) 30 FOR array=0 TO 255 40 ?(array+&4000)=array 50 NEXT 60 !&70=100 70 !&72=&4000 80 CALL word_array 90 PRINT"Word element is :";!&70 AND &FFFF 100 END 110 : 7600 DEF PROCword_array (subscript,arra y,addr) 7601 FOR pass=0 TO 3 STEP 3 7602 P%=addr 7603 [ 7604 OPT pass 7605 .word_array 7606 LDA subscript 7607 ASL A 7608 STA subscript 7609 LDA subscript+1 7610 ROL A 7611 STA subscript+1 7612 CLC 7613 LDA array 7614 ADC subscript 7615 STA array 7616 LDA array+1 7617 ADC subscript+1 7618 STA array+1 7619 LDY #0 7620 LDA (array),Y 7621 STA subscript 7622 INY 7623 LDA (array),Y 7624 STA subscript+1 7625 RTS 7626 ] 7627 NEXT 7628 ENDPROC
Program 12.8. PROCword_array - extracts a value from a one-dimensional word array.
To perform the multiplication on the two-byte subscript, a two-byte shift left is performed using the ASL/ROL combination (lines 7606 to 7611). The double subscript is then added to 'array' (lines 7612 to 7618) and the two-byte word extracted and placed in 'subscript' (lines 7619 to 7624).
10 REM *** 4 BYTE SIGNED INTEGER SORT *** 20 PROCsort32 (&70,&72,&74,&76,&C00) 30 INPUT"How many numbers to sort ?"c ount 40 ?&76=count-1 50 buffer=&4000 60 ?&74=0:?&75=&40 70 FOR random=0 TO count-1 80 !(buffer+4*random)=RND 90 NEXT random 100 CALL &C00 110 FOR look=0 TO count-1 120 PRINT !(buffer+4*look) 130 NEXT 140 END 150 : 7700 DEF PROCsort32 (one,two,vector,cou nt,addr ) 7701 FOR pass=0 TO 3 STEP 3 7702 P%=addr 7703 [ 7704 OPT pass 7705 .entry 7706 LDA vector 7707 STA two 7708 LDA vector+1 7709 STA two+1 7710 LDA #0 7711 STA loop 7712 .once_more 7713 LDY #0 7714 LDA two+1 7715 STA one+1 7716 LDA two 7717 STA one 7718 CLC 7719 ADC #4 7720 STA two 7721 BCC no_inc 7722 INC two+1 7723 .no_inc 7724 LDX #4 7725 SEC 7726 .subtract 7727 LDA (two),Y 7728 SBC (one),Y 7729 INY 7730 DEX 7731 BNE subtract 7732 BVC vclear 7733 EOR #&80 7734 .vclear 7735 EOR #0 7736 BPL no_swap 7737 DEY 7738 .swap 7739 LDA (one),Y 7740 STA store 7741 LDA (two),Y 7742 STA (one),Y 7743 LDA store 7744 STA (two),Y 7745 DEY 7746 BPL swap 7747 .no_swap 7748 INC loop 7749 LDA loop 7750 CMP count 7751 BNE once_more 7752 DEC count 7753 BNE entry 7754 RTS 7755 .loop 7756 EQUS" " 7757 .store 7758 EQUS" " 7759 ] 7760 NEXT 7761 ENDPROC
Program 12.9. PROCsort32 - sorts a list of four-byte values.
The procedure passes four variables used by the assembler. The variable 'vector' is, as its name implies, a zero page vector that the machine code expects to contain the start address ofthe array. On entry to the code at 'entry' this address is passed into the two working vectors 'one' and 'two' (how's that for originality!), lines 7705 to 7711. The sort routine is, in fact, a 'bubble sort' procedure. This works by working through the numbers in the array and comparing sets of two contiguous numbers. If the lower number is greater than the second number then they are swapped over. This process continues through all the numbers in the array until there are none left. The net effect is that numbers seem to bubble up through the array, thus the terminology. The disadvantage of a bubble sort is that it is very slow, although this is not so noticeable in machine code. However, it is not nearly as fast as the quicksort described later.
Let's get back to the program description. After seeding both vectors, the address in 'two' ,which points to the second of the two integers, is placed in 'one' (lines 7713 to 7717) and the vector 'two' is incremented by 4 to give it the address of the next vector in the array (lines 7718 to 7722). The X register is used as a byte counter so is initialised to 4 (line 7724) whereupon the integer 'one' is subtracted from the integer at 'two'. If on completion of the subtraction the overflow flag is set, it is necessary to reverse the sign of the most significant bit of the result now in the accumulator. This is performed in line 7733, and the EOR #&80 will set the negative flag to the value of the most significant bit of the accumulator. This process is particularly important as the negative flag is used to determine whether a swap is needed or not. If the flag is clear, no swap is indicated and a branch to 'no_swap' performed. The swap takes place, therefore, if the negative flag is set and is carried out by lines 7738 to 7746.
The final bytes of code (lines 7747 to 7753) test to see if the bubble sort has been completed, i.e. when a pass through it results in no swaps being performed after which control is passed back to BASIC. The BASIC demo routine at the start of the program pokes a random array of four-byte integers into a 'buffer' from &4000. After the sorting routine has been completed the array is displayed to show each four-byte integer in ascending order.
10 REM *** FORM NEW LIST FROM OLD *** 20 PROCnew_list (&4000,&4200,5,&C00) 30 FOR N=0 TO 100 40 ?(&4001+N)=N 50 NEXT 60 ?&4000=100 70 CALL &C00 80 C=?&4200 90 FOR N=0 TO C-1 100 PRINT?(&4201+N) 110 NEXT 120 END 130 : 7770 DEF PROCnew_list (source,new,step, addr) 7771 FOR pass=0 TO 3 STEP 3 7772 P%=addr 7773 [ OPT pass 7774 LDY #0 7775 LDX #0 7776 .next_byte 7777 LDA source+1,Y 7778 STA new+1,X 7779 INX 7780 TYA 7781 CLC 7782 ADC #step 7783 TAY 7784 CMP source 7785 BCC next_byte 7786 STX new 7787 RTS 7788 ] 7789 NEXT 7790 ENDPROC
Program 12.10. PROCnew_list - creates a sublist from a main list.
Fig. 12.3. (a) Assigning the key in a quickshort. (b) The array after the first quicksort pass. (c) The array after the second quicksort pass. (d) The array after the third quicksort pass.
Looking at Figure 12.3(d) carefully shows that it is divided into two halves. All the numbers on the left ofthe key are smaller than the key itself while those on the right are larger. In other words, the key has now found its final position in the list. The two sections ofthe list can now be sorted independently using new keys, and then the sections these provide and so on until the quicksort is completed.
Because the number of elements to be sorted reduces each time through the quicksort the time taken for it to complete its task is substantially quicker than the bubble sort method described earlier which processes the whole array each time through. In fact, to sort 1000 four-byte integers using the bubble sort would take around 50 seconds, compared with under two seconds for the quicksort. A BASIC version of the quicksort is only slightly slower than the assembler bubble sort!
10 REM *** FOUR BYTE INTEGER FASTSORT *** 20 PROCquick(&3900,&70,&72,&74,&76,&7 8,&7A,&80) 30 !&80=&5000:!(&80+2)=20 40 FOR N=0 TO 20 STEP4 50 !(&5000+N)=RND:NEXT 60 CALL fastsort 70 FOR N=0TO 20 STEP4 80 PRINT!(&5000+N):NEXT 90 END 100 : 7800 DEF PROCquick(softstk,left,right,c urrent_left,current_right,stack,middle,d ata) 7801 FOR pass=0 TO 3 STEP 3 7802 P%=&4000 7803 [ OPT pass 7804 .fastsort 7805 LDA #softstk MOD 256 7806 STA stack 7807 LDA #softstk DIV 256 7808 STA stack+1 7809 LDA data+2 7810 STA left 7811 LDA data+3 7812 STA left+1 7813 LDY #1 7814 .setup 7815 LDA (left),Y 7816 STA current_left,Y 7817 DEY 7818 BPL setup 7819 LDY #2 7820 .shift_two 7821 ASL current_left 7822 ROL current_left+1 7823 DEY 7824 BNE shift_two 7825 LDA data 7826 CLC 7827 ADC current_left 7828 STA right 7829 LDA data+1 7830 ADC current_left+1 7831 STA right+1 7832 LDA data 7833 SEC 7834 SBC #4 7835 STA left 7836 LDA data+1 7837 SBC #0 7838 STA left+1 7839 .save_value 7840 LDA left 7841 CLC 7842 ADC #4 7843 STA current_left 7844 LDA left+1 7845 ADC #0 7846 STA current_left+1 7847 LDA right 7848 STA current_right 7849 SEC 7850 SBC current_left 7851 BNE over 7852 LDA right+1 7853 SBC current_left+1 7854 BNE over 7855 JMP pull 7856 .over 7857 LDA right+1 7858 STA current_right+1 7859 JSR swap 7860 LDY #3 7861 .back 7862 LDA (current_left),Y 7863 STA key,Y 7864 DEY 7865 BPL back 7866 .adjust_right 7867 LDA current_right 7868 SEC 7869 SBC #4 7870 STA current_right 7871 BCS compare_hiright 7872 DEC current_right+1 7873 .compare_hiright 7874 LDA current_left+1 7875 CMP current_right+1 7876 BCC not_right 7877 LDA current_left 7878 CMP current_right 7879 BEQ equal 7880 .not_right 7881 LDX #4 7882 LDY #0 7883 SEC 7884 .compare_keyright 7885 LDA (current_right),Y 7886 SBC key,Y 7887 INY 7888 DEX 7889 BNE compare_keyright 7890 BVC no_mask 7891 EOR #&80 7892 .no_mask 7893 AND #&FF 7894 BPL adjust_right 7895 DEY 7896 .exchange 7897 LDA (current_right),Y 7898 STA (current_left),Y 7899 DEY 7900 BPL exchange 7901 .adjust_left 7902 LDA current_left 7903 CLC 7904 ADC #4 7905 STA current_left 7906 BCC compare_lefthigh 7907 INC current_left+1 7908 .compare_lefthigh 7909 LDA current_left+1 7910 CMP current_right+1 7911 BCC not_left 7912 LDA current_left 7913 CMP current_right 7914 BEQ equal 7915 .not_left 7916 LDX #4 7917 LDY #0 7918 SEC 7919 .compare_keyleft 7920 LDA key,Y 7921 SBC (current_left),Y 7922 INY 7923 DEX 7924 BNE compare_keyleft 7925 BVC no_mask_again 7926 EOR #&80 7927 .no_mask_again 7928 AND #&FF 7929 BPL adjust_left 7930 DEY 7931 .exchange_over 7932 LDA (current_left),Y 7933 STA (current_right),Y 7934 DEY 7935 BPL exhange_over 7936 BMI adjust_right 7937 .equal 7938 LDY #3 7939 .exc_loop 7940 LDA key,Y 7941 STA (current_left),Y 7942 DEY 7943 BPL exc_loop 7944 LDA current_left 7945 SEC 7946 SBC left 7947 STA word 7948 LDA current_left+1 7949 SBC left+1 7950 STA word+1 7951 LDA right 7952 SEC 7953 SBC current_left 7954 STA temp 7955 LDA right+1 7956 SBC current_left+1 7957 STA temp+1 7958 LDA word 7959 SEC 7960 SBC temp 7961 LDA word+1 7962 SBC temp+1 7963 BCC save_hi 7964 .save_lo 7965 LDY #0 7966 LDA left 7967 STA (stack),Y 7968 INY 7969 LDA left+1 7970 STA (stack),Y 7971 INY 7972 LDA current_left 7973 STA (stack),Y 7974 STA left 7975 INY 7976 LDA current_left+1 7977 STA (stack),Y 7978 STA left+1 7979 JMP update 7980 .save_hi 7981 LDY #2 7982 LDA right 7983 STA (stack),Y 7984 INY 7985 LDA right+1 7986 STA (stack),Y 7987 INY 7988 LDA current_left 7989 STA (stack),Y 7990 STA right 7991 INY 7992 LDA current_left+1 7993 STA (stack),Y 7994 STA right+1 7995 .update 7996 CLC 7997 LDA stack 7998 ADC #4 7999 STA stack 8000 BCC update1 8001 INC stack+1 8002 .update1 8003 JMP save_value 8004 .pull 8005 LDA stack 8006 SEC 8007 SBC #softstk MOD 256 8008 BNE pull1 8009 LDA stack+1 8010 SBC #softstk DIV 256 8011 BNE pull1 8012 RTS 8013 .pull1 8014 LDA stack 8015 SEC 8016 SBC #4 8017 STA stack 8018 BCS pull2 8019 DEC stack+1 8020 .pull2 8021 LDY #3 8022 .pull3 8023 LDA (stack),Y 8024 STA left,Y 8025 DEY 8026 BPL pull3 8027 JMP save_value 8028 .swap 8029 LDA current_right 8030 SEC 8031 SBC current_left 8032 AND #&F8 8033 STA middle 8034 LDA current_right+1 8035 SBC current_left+1 8036 STA middle+1 8037 LSR middle+1 8038 ROR middle 8039 LDA current_left 8040 CLC 8041 ADC middle 8042 STA middle 8043 LDA current_left+1 8044 ADC middle+1 8045 STA middle+1 8046 LDY #3 8047 .swap_loop 8048 LDA (current_left),Y 8049 STA word 8050 LDA (middle),Y 8051 STA (current_left),Y 8052 LDA word 8053 STA (middle),Y 8054 DEY 8055 BPL swap_loop 8056 RTS 8057 .key EQUD 0 8058 .word EQUW 0 8059 .temp EQUW 0 8060 ] NEXT 8061 ENDPROC
Program 12.11. PROCquick - implements a four-byte quickshort routine
The main areas operation of Program 12.11 are as follows:
Lines 7805 to 7808: Set up vector software stack which will be used to hold pointers and data for future reference by the routine.
Lines 7809 to 7813: Save base address of the integer array in 'left'.
Lines 7814 to 7818: Seed integer into the 'current_left'position.
Lines 7819 to 7855: Seed next integer into 'current_right' position and test to see if they are equal. If both are equal, then pull pointers and data from stack and move onto next subsection.
Lines 7856 to 7860: If items are not equal perform the swap.
Lines 7861 to 7865: Then save key for future reference.
Lines 7866 to 7900: Compare key with integers to the right of it and perform the swap if greater.
Lines 7901 to 7936: Compare key with integers to the left of it and perform swap if less than.
Lines 7937 to 8043: Place key back in 'current_left'.
Lines 7944 to 7963: Now save pointers of unsorted sections of integer array on software stack. Concentrate on smallest section first.
Lines 7964 to 8003: Routine to push pointers and data items onto software stack.
Lines 8004 to 8027: Routine to pull all pointers and data items off software stack.
Lines 8028 to 8055: Subroutine to perform the key integer swap.
Two sets of two variables are used by the assembler routine. The boundaries of the current section half being sorted are held in 'left' and 'right'. The current left- and right-hand numbers being tested with the key are held in 'current_left'and 'current_right' respectively. The BASIC primer sets up an array of random integer values at &5000. After calling the quickshort, the sorted array is displayed (lines 20 to 90).
Program 12.2
Procedure title : PROCordered_add (also requires PROCbin_search)
Variables required : byte, list, pos, temp, addr
Line numbers : 7100 to 7194
Length : 115 bytes
Zero page requirements : 5 bytes
Registers changed : A, X, Y
Program 12.3
Procedure title : PROCordered_del (also requires PROCbin_search)
Variables required : byte, list, pos, temp, addr
Line numbers : 7200 to 7276
Length : 87 bytes
Zero page requirements : 5 bytes
Registers changed : A, X, Y
Program 12.4
Procedure title : PROCmax_min_list
Variables required : min, max, list, addr
Line numbers : 7300 to 7330
Length : 36 bytes
Zero page requirements : 4 bytes
Registers changed : A, X, Y
Program 12.5
Procedure title : PROCunordered_del
Variables required : byte, list, addr
Linc numbers : 7400 to 7431
Length : 36 bytes
Zero page requirements : 3 bytes
Registers changed : A, X, Y
Program 12.6
Procedure title : PROCbyte_array
Variables required : subscript, array, addr
Line numbers : 7500 to 7519
Length : 18 bytes
Zero page requirements : 3 bytes
Registers changed : A, Y
Program 12.7
Procedure title : PROCtwodim_byte
Variables required : subscriptl, subscript2, sub_size, temp, array, addr
Line numbers : 7530 to 7577
Length : 68 bytes
Zero page requirements : I0 bytes
Registers changed : A, X, Y
Program 12.8
Procedure title : PROCword_array
Variables required : subscript, array, addr
Line numbers : 7600 to 7628
Length : 34 bvtes
Zero page requirements : 3 byies
Registers changed : A, X, Y
Program 12.9
Procedure title : PROCsort32
Variables required : one, two, vector, count, addr
Line numbers : 7700 to 7761
Length : 86 bytes
Zero page requirements : 8 bytes
Registers changed : A, X, Y
Program 12.10
Procedure title : PROCnew_list
Variables required : source, new, step, addr
Line numbers : 7770 to 7790
Length : 25 bytes
Zero page requirements : 1 byte
Registers changed : A, X, Y
Program 12.11
Procedure title : PROCquick
Variables requircd : softstk, left, right, current_left, current_right, stack, middle, data
Line numbers : 7800 to 8061
Length : 437 bytes
Zero page requirements : 14 bytes
Registers changed : A, X, Y
The routines included are:
Program 13.1 | : Perform a CLS. |
Program 13.2 | : Perform VPOS and POS. |
Program 13.3 | : Perform SPC. |
Program 13.4 | : Perform STRING$. |
Program 13.5 | : Perform TAB (X). |
Program 13.6 | : Perform TAB (X,Y). |
Program 13.7 | : Perform GET. |
Program 13.8 | : Perform INKEY. |
Program 13.9 | : Super quick key test. |
Program 13.10 | : Read line. |
Program 13.11 | : Read a VDU definition. |
Program 13.12 | : Hexadecimal to ASCII. |
Program 13.13 | : Packed BCD to ASCII. |
Program 13.14 | : ASCIl to packed BCD. |
10 REM*** CLEAR SCREEN -CLS *** 20 PROCcls (&C00) 30 PRINT"PRESS A KEY TO CLEAR SCREEN" 40 A=GET 50 CALL &C00 60 END 70 : 8000 DEF PROCcls (addr) 8001 P%=addr 8002 [ 8003 .clear_screen 8004 LDA #12 8005 JSR &FFEE 8006 RTS 8007 ] 8008 ENDPROC
Program 13.1. PROCcls - performs CLS
Knowing where the cursor is at a particular time is another useful function to perform. In BASIC, the POS and VPOS functions return the horizontal (X axis) and vertical (Y axis) components of the cursor. These two functions have a direct equivalent within the MOS, an OSBYTE &86 call. Program 13.2 shows that the X and Y coordinates of the cursor are returned in the respective index registers which can be saved for evaluation.
10 REM *** DO POS & VPOS *** 20 PROCcursor (&70,71,&C00) 30 CLS 40 PRINTTAB(10,10); 50 CALLcursor 60 PRINT'"Cursor positions were :" 70 PRINT"POS = ";?&70 80 PRINT"VPOS= ";?&71 90 END 100 : 8010 DEF PROCcursor (pos,vpos,addr) 8011 P%=&C00 8012 [ OPT 2 8013 .cursor 8014 LDA #&86 8015 JSR &FFF4 8016 STX pos 8017 STY vpos 8018 RTS 8019 ] 8020 ENDPROC
Program 13.2. PROCcursor - returns the X,Y coordinates of the text cursor.
Program 13.3 imitates the BASIC command SPC, to print a specified number of spaces from the current cursor position. The number of spaces to be printed should be passed into the procedure via the variable 'spc'. At assembly time, this variable is treated as an immediate value being loaded directly into the X register to act as a simple loop control. Prior to entering 'loop', the accumulator is loaded with 32, the ASCIl code of a space, and the required number is printed.
10 REM *** DO MACHINE CODE SPC *** 20 CLS 30 INPUT"How many spaces ?"spc 40 PROCspace (spc,&C00) 50 CALL space 60 END 70 : 8030 DEF PROCspace (spc,addr) 8031 P%=addr 8032 [ OPT 2 8033 .space 8034 LDX #spc 8035 LDA #32 8036 .loop 8037 JSR &FFEE 8038 DEX 8039 BNE loop 8040 RTS 8041 ] 8042 ENDPROC
Program 13.3. PROCspace - performs SPC.
STRING$ is a BASIC command that allows a specified number of the same string to be printed. This can result in a great saving of memory space. For example, it is much neater to print 40 asterisks using
PRINT STRING$ (40,"*")
rather than enclosing 40 asterisks inside quotes or from a machine code point of view in an ASCII data table. Program 13.4 emulates this command by printing a string located at 'buffer', 'num' number of times.
10 REM *** DO STRING$ *** 20 CLS 30 INPUT"Enter your string :"$&C50 40 INPUT"How many times :"num 50 PROCstring (&C50,num,&C00) 60 CALL string 70 END 80 : 8050 DEF PROCstring (buffer,num,addr) 8051 FOR pass=0 TO 2 STEP 2 8052 P%=addr 8053 [ OPT pass 8054 .string 8055 LDY #num 8056 .count 8057 LDX #0 8058 .next_chr 8059 LDA buffer,X 8060 JSR &FFE3 8061 INX 8062 CMP #13 8063 BNE next_chr 8064 DEY 8065 BNE count 8066 RTS 8067 ] 8068 NEXT 8069 ENDPROC
Program 13.4. PROCstring - performs STRING$
The program commences by loading the Y register with the 'num' count (line 8055) and then setting the X register to zero (line 8057), which is to be used as an absolute index into 'buffer'. The get and display loop is embodied in lines 8058 to 8063. Each character is extracted from 'buffer' and printed in turn until a carriage return has been printed. The Y register is decremented and the 'count' loop repeated until it reaches zero. Note that in the code the carriage return at the end of the string (put there by BASIC's INPUT statement - line 30) is printed, so that each string occupies a new line rather than being printed continuously across the screen. The CMP #13 test could be performed earlier so that the program exists before issuing a return if so required.
Positioning text on the screen is performed using the TAB function. There are two bytes of TAB, namely TAB(X) and TAB(X,Y). The simplest way to perform a TAB(X) is to print the move cursor right code, ASCII 9, the required number of times. Program 13.5 details the assembler, the required value of X passing into the procedure through 'xpos'. Once again, a simple loop is used printing the code 9 through OSWRCH until the X register has been decremented to zero. It is worth bearing in mind that this routine is in fact different from the one given in Program 13.3 as it has no effect on any text the cursor passes over. Only the cursor is moved and no spaces are output as in the former program.
10 REM *** DO TAB(X)*** 20 CLS 30 INPUT"Number of tabs :"xpos 40 PROCtabx(xpos,&C00) 50 CLS 60 CALL tabx 70 PRINT"*"' 80 PRINT"* marks the TAB position" 90 END 100 : 8080 DEF PROCtabx (xpos,addr) 8081 P%=addr 8082 [ OPT 2 8083 .tabx 8084 LDA #9 8085 LDX #xpos 8086 .xtab 8087 JSR &FFEE 8088 DEX 8089 BNE xtab 8090 RTS 8091 ] 8092 ENDPROC
Program 13.5. PROCtabx - performs TAB(X)
TAB(X,Y) is performed through OSWRCH using the driver code 31 followed by first the X and then the Y coordinate to tab to. Program 13.6 uses immediate addressing to pass the two TAB parameters into the assembler via 'xpos'and 'ypos'.
10 REM *** DO TAB(X,Y) *** 20 CLS 30 INPUT"Tab X position :"xpos 40 INPUT"Tab Y position :"ypos 50 PROCtabxy (xpos,ypos,&C00) 60 CLS 70 CALL tabxy 80 PRINT"*"' 90 PRINT"* marks the TAB position" 100 END 110 : 8100 DEF PROCtabxy (xpos,ypos,addr) 8101 P%=addr 8102 [ OPT 2 8103 .tabxy 8104 LDA #31 8105 JSR &FFEE 8106 LDA #xpos 8107 JSR &FFEE 8108 LDA #ypos 8109 JSR &FFEE 8110 RTS 8111 ] 8112 ENDPROC
Program 13.6. PROCtabxy - performs TAB(X,Y).
10 REM *** TEST FOR KEY ** 20 CLS 30 PRINT"Press key to test for"; 40 chr$=GET$ 50 chr=ASC(chr$) 60 PROCgetkey(chr,&C00) 70 PRINT''"Press ";chr$;" to end"; 80 CALL getstring 90 PRINT'''"Finished!" 100 END 110 : 8120 DEF PROCgetkey (chr,addr) 8121 FOR pass=0 TO 2 STEP 2 8122 P%=addr 8123 [ OPT pass 8124 .getstring 8125 JSR &FFE0 8126 CMP #&1B 8127 BNE no_escape 8128 LDA #&7E 8129 JSR &FFF4 8130 .no_escape 8131 CMP #chr 8132 BNE getstring 8133 RTS 8134 ] 8135 NEXT 8136 ENDPROC
Program 13.7. PROCgetkey - performs GET.
Using OSBYTE &81, an INKEY timed input can be performed. The index registers are used to hold the wait period which is specified in centiseconds. Program 13 .8 shows how it is set up in the procedure PROCinkey. Prior to the actual OSBYTE call an *FX15,1 is performed to flush all input buffers (lines 8145 to 8148). The wait period is passed into the assembler via the variable 'wait', the high and low bytes are loaded into the respective registers usingthe MOD and DIV operators (lines 8149 to 8152). As with the previous procedure, the ESCAPE key should be tested for and acknowledged with the appropriate call if need be (lines 8153 to 8156). Note that in this instance the escape code is returned in the Y register. If a key is detected in the allotted time period it is returned from OSBYTE in the X register and both the Y register and carry flag are clear. If no character is detected within the time period, Y returns containing &FF and the carry is set.
10 REM *** DO MACHINE CODE INKEY *** 20 PROCinkey (1000,&70,&C00) 30 CLS 40 PRINT"Press key within time limit " 50 CALL inkey 60 PRINT"Key pressed was :";CHR$(?&70 ) 70 END 80 : 8140 DEF PROCinkey (wait,result,addr) 8141 FOR pass=0 TO 2 STEP 2 8142 P%=addr 8143 [ OPT pass 8144 .inkey 8145 LDA #15 8146 LDX #1 8147 LDY #0 8148 JSR &FFF4 8149 LDA #&81 8150 LDX #wait MOD 256 8151 LDY #wait DIV 256 8152 JSR &FFF4 8153 CPY #&1B 8154 BNE no_escape 8155 LDA #&7E 8156 JSR &FFF4 8157 .no_escape 8158 STX result 8159 RTS 8160 ] 8161 NEXT 8162 ENDPROC
Program 13.8. PROCinkey - performs INKEY.
OSBYTE &81 can also be used to perform a single keyboard scan if it is called with the Y register holding &FF and the X register the negative INKEY value.
The demonstration program performs an INKEY(1000) equivalent, which basically causes the MOS to look at the keyboard for 10 seconds or until a key is pressed.
Both of the above two routines suffer from one drawback. They are slow! Well, in machine code terms they are. Consider that the fastest MOS-based routine, using OSBYTE &81 with X holding the negative inkey value will take at least 300 cycles and anything up to 1200 cycles. Program 13.9 does the whole scan in a mere 12 cycles and what's more it can test for two keys being pressed at once! The code assembled by PROCkeytest looks at locations &EC and &ED. If a key is pressed at any time then the MOS places zero into &ED and &EC contains the key's number. The actual numbers stored by the MOS are internal key numbers +128. For almost all purposes the internal key numbers are the negative INKEY numbers made positive and then decremented by one. For example, C is equal to INKEY(-83) so its internal number is calculated as ABS(-83)-1 or 83-1=82. Testing for C using 'keytest' therefore requires a test for 82+128=210.
If two keys are pressed almost simultaneously then &ED contains the number of the first key pressed and &EC the second key. If no keys are detected then both these bytes are zero.
10 REM *** QUICK TEST FOR KEY *** 20 PROCkeytest (&900) 30 VDU15 40 CALL start 50 END 60 : 8300 DEFPROCkeytest (addr) 8301 FOR pass=0 TO 2 STEP 2 8302 P%=addr 8303 [ 8304 OPT pass 8305 .start 8306 LDA&ED 8307 BEQ check_EC 8308 JSR valid_key 8309 BEQ check_EC 8310 JSR&FFEE 8311 .check_EC 8312 LDA&EC 8313 BEQ start 8314 JSR valid_key 8315 BEQ start 8316 JSR&FFEE 8317 JMP start 8318 .valid_key 8319 CMP#&F0 8320 BNE next1 8321 PLA 8322 PLA 8323 LDA#15 8324 JSR&FFF4 8325 RTS 8326 .next1 8327 CMP#&E1 8328 BNE next2 8329 LDA#&5A 8330 RTS 8331 .next2 8332 CMP#&C2 8333 BNE next3 8334 LDA#&58 8335 RTS 8336 .next3 8337 CMP#&D2 8338 BNE next4 8339 LDA#&43 8340 RTS 8341 .next4 8342 CMP#&E3 8343 BNE next5 8344 LDA#&56 8345 RTS 8346 .next5 8347 LDA#0 8348 RTS 8349 ] 8350 NEXT 8351 ENDPROC
Program 13.9. PROCkeytest - a 12-cycle key test.
As it stands, the routine has been set up to look for Z, X, C, V and ESCAPE. The hex begins by peeking &ED. If this is zero then &EC can be tested straightaway so the branch to 'check_EC' is performed (lines 8306 to 8307). The subroutine 'valid_key' tests for each of the above keys. The first tested is ESCAPE, code &F0 (line 8319). If it is detected then the RTS address is pulled from the stack and the MOS entered with 15 in the accumulator to handle the ESCAPE. The following bytes then look for each key in turn. The codes for each are:
&E1 = Z
&C2 = X
&D2 = C
&E3 = V
If any of these compare, the letter is printed out. The 'check_EC' routine works exactly the same.
If you run the program and then press either the Z, X, C, or V keys then you'll see just how fast this key test routine is. The screen half fills with letters before you can lift your finger off the key! Finally, I should point out that this method is not condoned by Acorn as it is MOS-dependent. It will work on OS 1.0 and OS 1.2 but I haven't tried it on OS 0.1 so it may not work if you are using this version of MOS. The GET single key type routines could be employed to read in a string of characters, placing each one into a defined buffer until a set number of characters is reached or a return character detected. This does involve some extra coding, though, as a loop and buffer would need to be implemented. A neater way is to use the MOS line input routine OSWORD &00. This is a very useful call as it allows a number of parameters to be specified regarding the characters being input, Figure 13.1 details the parameter block. The first two bytes contain the address ofthe input buffer, the second byte the maximum number of characters to be placed in the buffer, while the last two bytes determine the maximum and minimum acceptable ASCII characters that will be accepted and placed in the buffer!
This OSWORD call does have one major disadvantage, though. Although only characters in the specified ASCII range will be placed into the buffer, any other characters presses will be echoed to the screen even though they are not deposited in the buffer. Program 13.10 provides a suitable procedure. The parameter block address for the call is defined in 'block' while B% determines the input line buffer location; L% the number of characters acceptable, i.e. the buffer's maximum length; and 'max', 'min' the acceptable character range.
XY+0 | : LSB of input buffer address |
XY+1 | : MSB of input buffer address |
XY+2 | : Maximum number of characters |
XY+3 | : Minimum ASCII value of character acceptable |
XY+4 | : Maximum ASCII value of character acceptable |
10 REM ** READ LINE FROM INPUT ** 20 PROCinputline(&70,&C00,10,ASC"A",A SC"Z",&4000) 30 CALL &4000 40 PRINT$&C00 50 END 8170 DEF PROCinputline(block,B%,L%,max, min,addr) 8171 FOR pass=0 TO 3 STEP3 8172 P%=addr 8173 [ OPT pass 8174 LDA #B% MOD 256 8175 STA block 8176 LDA #B% DIV 256 8177 STA block+1 8178 LDA #L% 8179 STA block+2 8180 LDA #max 8181 STA block+3 8182 LDA #min 8183 STA block+4 8184 LDA #0 8185 LDX #block MOD 256 8186 LDY #block DIV 256 8187 JSR &FFF1 8188 RTS 8189 ] 8190 NEXT 8191 ENDPROC
Program 13.10. PROCinputline - input a line of text
Program 13.11 will read the bit map definition of any ASCII or defined VDU character. OSWORD &9A performs the task and all that is required prior to the read is to define a nine-byte parameter block, 'block' in the program. The ASCII code of the character to be read should be located in the first byte ofthe parameter block and on return the following eight bytes contain the character's definition starting with the top row of the character.
10 REM **READ VDU CHR DEFINITION** 20 CLS 30 PRINT"PRESS A KEY TO DISPLAY ITS D EFINITION"; 40 chr$=GET$ 50 chr=ASC(chr$) 60 PROCread_chr(&70,chr,&4000) 70 CALL &4000 80 CLS 90 PRINT"THE DEFINITION OF ";chr$;" I S :" 100 FOR N=&71 TO &78:PRINT?N:NEXT 110 END 8200 DEF PROCread_chr(block,chr,addr%) 8201 FOR pass=0 TO 3 STEP 3 8202 P%=addr% 8203 [ OPT pass 8204 LDA #chr 8205 STA block 8206 LDA #10 8207 LDX #block MOD 256 8208 LDY #block DIV 256 8209 JSR &FFF1 8210 RTS 8211 ] 8212 NEXT 8213 ENDPROC
Program 13.11. PROCread_chr - reads the eight-byte definition of any character.
10 REM *** SIMPLE HEX TO ASCII *** 20 PROChex_asc(&70,&71,&72,&C00) 30 ?&70=255 40 CALL &C00 50 PRINT''' 60 PRINTCHR$(?&71);CHR$(?&72) 70 END 80 : 8230 DEF PROChex_asc(byte,high,low,addr ) 8231 P%=addr 8232 [ 8233 LDA byte 8234 AND #15 8235 SED 8236 CLC 8237 ADC #&90 8238 ADC #&40 8239 CLD 8240 STA low 8241 LDA byte 8242 LSR A 8243 LSR A 8244 LSR A 8245 LSR A 8246 SED 8247 CLC 8248 ADC #&90 8249 ADC #&40 8250 CLD 8251 STA high 8252 RTS 8253 ] 8254 ENDPROC
Program 13.12. PROChex_asc - convert a hexadecimal number into two
ASCII values.
Adding &90 to the binary values &A to &F results in a byte in the range &00 to &05 with the carry set. A further addition of &40 (plus the set carry) converts this to values &41 to &46, the ASCIl codes for the letters A to F.
The byte is then saved and the next four bits treated likewise after shifting them into the lower nibble position with four logical shifts. Both ASCII codes are stored at 'high' and 'low' for future reference. If you wish not to save the two results but to print them directly then remember that the high nibble must be treated first and then the low nibble as the digits are printed, most significant first, on the screen.
Program 13.13 converts a packed BCD digit into its component ASCII codes. To perform the conversion the byte must first be transformed into its two component nibbles, which should be placed in the low nibble position before having bit 5 forced to 1 using ORA #&30. The high nibble is treated first. After loading the packed byte from 'bcd' it is pushed onto the hardware stack for future reference. The high nibble is then shifted into the low nibble position (lines 8263 to 8268). Bit 5 is then forced and the ASCII character code placed in 'high' - it could at this point be printed using JSR &FFEE instead. The stack is pulled and the high nibble masked out with an AND (lines 8271 to 8272). Bit 5 is forced and the result placed in 'low' (lines 8273 to 8274).
10 REM *** PACKED BCD TO ASCII *** 20 PROCbcd_ascii(&70,&71,&72,&C00) 30 ?&70=&12 40 CALL &C00 45 PRINT'' 50 PRINT CHR$(?&72); CHR$(?&71) 60 END 70 : 8260 DEF PROCbcd_ascii(bcd,low,high,add r) 8261 P%=addr 8262 [ 8263 LDA bcd 8264 PHA 8265 LSR A 8266 LSR A 8267 LSR A 8268 LSR A 8269 ORA #&30 8270 STA high 8271 PLA 8272 AND #15 8273 ORA #&30 8274 STA low 8275 RTS 8276 ] 8277 ENDPROC
Program 13.13. PROCbcd_ascii - converts a packed BCD byte into two
ASCII values.
Performing the reverse conversion, two ASCII digits to packed BCD, involves reversing the procedure as depicted in Program 13.14. The high digit is extracted from 'high' shifted right so that bit 5 is lost and only the binary bits remain. This byte is pushed onto the hardware stack. The 'low' digit is loaded into the accumulator, the low nibble preserved, and the stack pointer copied into the X register (lines 8289 to 8291). The two digits have now been stripped of bit 5, and all that is now required is to merge them together. This is done by logically ORing the two together (lines 8292). The X register is incremented and copied back into the stack pointer thus 'popping' the byte from the stack. Finally the result is placed at 'bcd'.
10 REM *** ASCII TO PACKED BCD *** 20 PROCasc_bcd(&70,&71,&72,&C00) 30 ?&70=ASC("6") 40 ?&71=ASC("9") 50 CALL &C00 60 PRINT~?&72 70 END 80 : 8280 DEF PROCasc_bcd(high,low,bcd,addr) 8281 P%=addr 8282 [ 8283 LDA high 8284 ASL A 8285 ASL A 8286 ASL A 8287 ASL A 8288 PHA 8289 LDA low 8290 AND #15 8291 TSX 8292 ORA &101,X 8293 INX 8294 TXS 8295 STA bcd 8296 RTS 8297 ] 8298 ENDPROC
Program 13.14. PROCasc_bcd - converts two ACII values into a packed
BCD byte.
Procedure title : PROCcursor
Variables required : pos, vpos, addr
Line numbers : 8010 to 8020
Length : 10 bytes
Zero page requirements : 2 bytes
Registers changed : A, X, Y
Program 13.3
Procedure title : PROCspace
Variables required : spc, addr
Line numbers : 8030 to 8042
Length : 11 bytes
Zero page requirements : none
Registers changed : A, X
Program 13.4
Procedure title : PROCstring
Variables required : buffer, num, addr
Line numbers : 8050 to 8069
Length : 19 bytes
Zero page requirements : none
Registers changed : A, X, Y
Program 13.5
Procedure title : PROCtabx
Variables required : xpos, addr
Line numbers : 8080 to 8092
Length : 17 bytes
Zero page requirements : none
Registers changed : A, X
Program 13.6
Procedure title : PROCtabxy
Variables required : xpos, ypos, addr
Line numbers : 8100 to 8112
Length : 16 bytes
Zero page requirements : none
Registers changed : A
Program 13.7
Procedure title : PROCgetkey
Variables required : chr, addr
Line numbers : 8120 to 8136
Length : 17 bytes
Zero page requirements : none
Registers changed : A
Program 13.8
Procedure title : PROCinkey
Variables required : wait, result, addr
Line numbers : 8150 to 8162
Length : 31 bytes
Zero page requirements : 1 byte
Registers changed : A, X, Y
Program 13.9
Procedure title : PROCkeytest
Variables required : addr
Line numbers : 8300 to 8351
Length : 69 bytes
Zero page requirements : none
Registers changed : A
Program 13.10
Procedure title : PROCinputline
Variables required : block, B%, L%, max, min, addr
Line numbers : 8170 to 8191
Length : 26 bytes
Zero page requirements : 5 bytes
Registers changed : A, X, Y
Program 13.11
Procedure title : PROCread_chr
Variables required : block, chr, addr
Line numbers : 8200 to 8213
Length : 14 bytes
Zero page requirements : 9 bytes
Registers changed : A, X, Y
Program 13.12
Procedure title : PROChex_asc
Variables required : byte, high, low, addr
Line numbers : 8230 to 8254
Length : 29 bytes
Zero page requirements : 3 bytes
Registers changed : A
Program 13.13
Procedure title : PROCbcd_ascii
Variables required : bcd, low, high, addr
Line numbers : 8260 to 8277
Length : 19 bytes
Zero page requirements : 3 bytes
Registers changed : A
Program 13.14
Procedure title : PROCasc_bcd
Variables required : high, low, bcd, addr
Line numbers : 8280 to 8289
Length : 20 bytes
Zero page requirements : 3 bytes
Registers changed : A, X
Program 14.1 | : Find highest IRQ. |
Program 14.2 | : Timer 1 delay. |
Program 14.3 | : Timer 2 delay. |
Program 14.4 | : One second delay. |
Program 14.5 | : Save all processor registers. |
Program 14.6 | : Restore all processor registers. |
Program 14.7 | : Two-byte incrementing counter. |
Program 14.8 | : Two-byte decrementing counter. |
10 REM *** FIND HIGHEST IRQ *** 20 REM ***needs extra user coding*** 30 REM **to run sequence correctly** 40 : 9000 DEF PROChighestIRQ (temp,addr) 9001 FOR pass=0 TO 3 STEP 3 9002 P%=addr 9003 [ 9004 OPT pass 9005 .findIRQ 9006 LDA #&96 9007 LDX #&6D 9008 JSR &FFF4 9009 TYA 9010 BPL next_device 9011 PHA 9012 LDA #&96 9013 LDX #&6E 9014 JSR &FFF4 9015 STY temp 9016 PLA 9017 AND temp 9018 ASL A 9019 BMI timer1 9020 ASL A 9021 BMI timer2 9022 ASL A 9023 BMI cb1 9024 ASL A 9025 BMI cb2 9026 ASL A 9027 BMI shift_reg 9028 ASL A 9029 BMI ca1 9030 ASL A 9031 BMI ca2 9032 JMP error 9033 .timer1 9034 JMP T1service 9035 .timer2 9036 JMP T2service 9037 .cb1 9038 JMP cb1service 9039 .cb2 9040 JMP cb2service 9041 .shift_reg 9042 JMP srservice 9043 .ca1 9044 JMP ca1service 9045 .ca2 9046 JMP ca2service 9047 .error 9048 \ error service routine here 9049 .next_device 9050 \ more polling here 9051 ] 9052 NEXT 9053 ENDPROC
Program 14.1. PROChighestIRQ - locates the highest priority interrupt.
In Program 14.1 I have used a left to right priority system, so that bit 6 has a greater priority than bit 5. Therefore an interrupt on Tl interrupt enable has a greater priority than an interrupt on T2 and so forth. You can arrange your own priorities to suit, though the test procedure might not be a simple shift and branch sequence, and more complex coding might be required.
10 REM *** MILLISECOND DELAY USING T1 *** 20 PROCtimerone_delay (&C00) 30 CALL millisec1 40 END 50 : 9060 DEF PROCtimerone_delay (addr) 9061 FOR pass=0 TO 3 STEP 3 9062 P%=addr 9063 [ 9064 OPT pass 9065 .millisec1 9066 LDA #&97 9067 LDX #&6B 9068 LDY #0 9069 JSR &FFF4 9070 LDX #&64 9071 LDY #&E8 9072 JSR &FFF4 9073 LDX #&65 9074 LDY #3 9075 JSR &FFF4 9076 LDA #&40 9077 .loop 9078 BIT &FE6D 9079 BEQ loop 9080 LDA &FE64 9081 RTS 9082 ] 9083 NEXT 9084 ENDPROC
Program 14.2. PROCtimerone_delay - a one millisecond delay using Timer 1.
Program 14.3 performs a similar delay using Timer 2. Only the addresses in the program and the bit mask change.
10 REM *** MILLISECOND DELAY USING T2 *** 20 PROCtimertwo_delay (&C00) 30 CALL millisec 40 END 50 : 9100 DEF PROCtimertwo_delay (addr) 9101 FOR pass=0 TO 3 STEP 3 9102 P%=addr 9103 [ 9104 OPT pass 9105 .millisec 9106 LDA #&97 9107 LDX #&6B 9108 LDY #0 9109 JSR &FFF4 9110 LDX #&68 9111 LDY #&E8 9112 JSR &FFF4 9113 LDX #&69 9114 LDY #3 9115 JSR &FFF4 9116 LDA #&20 9117 .loop 9118 BIT &FE6D 9119 BEQ loop 9120 LDA &FE6B 9121 RTS 9122 ] 9123 NEXT 9124 ENDPROC
Program 14.3. PROCtimertwo_delay - a one millisecond delay using Timer 2.
The timers are fine for providing very short delay loops but for substantial delays they are not really suitable. Program 14.4 will provide a 1-second delay. It does this by just executing a series of timed loops. As the clock on the Beeb operates at 2MHz, the delay loop need only count out 2000000 cycles to create the delay.
10 REM *** 1.0 SECOND DELAY *** 20 PROCdelay (&C00) 30 INPUT"How many seconds delay ?: "w ait 40 TIME=0 50 FOR loop=1 TO wait 60 CALL &C00 70 NEXT 80 time%=TIME 90 time%=(time%/100) 100 PRINT"Time taken was :";time%; 110 PRINT" second(s)" 120 END 130 : 9130 DEF PROCdelay (addr) 9131 FOR PASS=0 TO 3 STEP 3 9132 P%=addr 9133 [ OPT PASS 9134 PHP 9135 PHA 9136 TXA 9137 PHA 9138 TYA 9139 PHA 9140 LDY outer 9141 .loop1 9142 TYA 9143 PHA 9144 LDX inner 9145 .loop2 9146 LDY #5 9147 .loop3 9148 DEY 9149 BNE loop3 9150 DEX 9151 BNE loop2 9152 LDY #2 9153 .loop4 9154 DEY 9155 BNE loop4 9156 PLA 9157 TAY 9158 DEY 9159 BNE loop1 9160 LDY fine 9161 .loop5 9162 DEY 9163 BNE loop5 9164 PLA 9165 TAY 9166 PLA 9167 TAX 9168 PLA 9169 PLP 9170 RTS 9171 .outer 9172 EQUB 251 9173 .inner 9174 EQUB 0 9175 .fine 9176 EQUB 197 9177 ] 9178 NEXT 9179 ENDPROC
Program 14.4. PROCdelay - a one second delay loop.
The main loop counter is provided by 'outer' while the finer inner loop counter is 'inner'. The program commences by pushing all processor registers onto the stack thus preserving their status on return. This process takes 16 cycles (lines 9134 to 9139). The Y register is then loaded with 'outer' (4 cycles line 9140) and 'loopl' entered. This major outer loop has a smaller loop within it between lines 9145 and 9151 which takes a total of 256*31-1 cycles or 7935 cycles to execute. As the outer 'loop1' is controlled by the contents of the Y register, 251, the main loop between lines 9141 and 9159 takes 251*7964-1 or 1998963 cycles to run. In both cases, the -1 is needed because the final branch does not take place and therefore only uses 2 cycles and not the 3 allowed in the calculation. The final 'fine' loop plus restoring the registers add a further 30+984 (the loop in lines 9162 to 9163) cycles to the overall delay. The total delay provided by the loop is therefore calculated as 1998963 + 16 + 4 + 30 + 984 = 1999997 cycles. This is obviously three cycles short of the desired delay, which doesn't really bear thinking about!
The BASIC demo asks how long a delay you would like. Because there is an overhead in the BASIC operations, don't be too surprised if you enter 20 in response to the prompt and get an answer of 23 seconds back. The extra three seconds were created by the BASIC interpreter working through the program! Now a question. Add the following line to the program -
25 CLS
and run it. Why does it seem to work twice as quick? Don't expect the answer; I'm still trying to fathom it out for myself!
10 REM*SAVE ALL PROCESSOR REGISTERS* 20 REM*DOES NOT HAVE RTS MUST BE * 30 REM*USED DIRECTLY IN CODE. * 40 : 9200 DEF PROCpush_all(addr) 9201 P%=addr 9202 [ OPT 2 9203 .pushall 9204 PHP 9205 PHA 9206 TXA 9207 PHA 9208 TYA 9209 PHA 9210 ] 9211 ENDPROC
Program 14.5. PROCpush_all - save all processor registers on hardware
stack.
10 REM*RESTORE ALL PROCESSOR REGISTER S* 20 REM*DOES NOT HAVE RTS MUST BE * 30 REM*USED DIRECTLY IN CODE. * 40 : 9220 DEF PROCpull_all(addr) 9221 P%=addr 9222 [ OPT 2 9223 .pullall 9224 PLA 9225 TAY 9226 PLA 9227 TAX 9228 PLA 9229 PLP 9230 ] 9231 ENDPROC
Program 14.6. PROCpull_all - restore all processor registers off of the hardware stack.
10 REM *** TWO BYTE COUNTER *** 20 PROCinc_count (5000,&70,&C00) 25 CALL seed_value 30 REPEAT 40 PRINT?&71*256+?&70 50 CALL plus_one 60 UNTIL ?&71=0 70 END 80 : 9240 DEF PROCinc_count (num,block,addr) 9241 FOR pass=0 TO 3 STEP 3 9242 P%=addr 9243 [ OPT pass 9244 .seed_value 9245 LDA #num MOD 256 9246 STA block 9247 LDA #num DIV 256 9248 STA block+1 9249 .plus_one 9250 INC block 9251 BNE no_inc 9252 INC block+1 9253 .no_inc 9254 RTS 9255 ] 9256 NEXT 9257 ENDPROC
Program 14.7. PROCinc_count - performs a double-byte increment.
10 REM *** DOUBLE BYTE DECREMENT *** 20 PROCdec_count (5000,&70,&C00) 25 CALL seed_value 30 REPEAT 40 PRINT?&71*256+?&70 50 CALL minus_one 60 UNTIL ?&71=0 70 END 80 : 9260 DEF PROCdec_count (num,block,addr) 9261 FOR pass=0 TO 3 STEP 3 9262 P%=addr 9263 [ OPT pass 9264 .seed_value 9265 LDA #num MOD256 9266 STA block 9267 LDA #num DIV256 9268 STA block+1 9269 .minus_one 9270 LDA block 9271 BNE no_dec 9272 DEC block+1 9273 .no_dec 9274 DEC block 9275 RTS 9276 ] 9277 NEXT 9278 ENDPROC
Program 14.8. PROCdec_count - performs a double-byte decrement.
The application program in lines 20 to 60 shows that once a count value has been seeded only 'plus_one' should be called to increment the block.
Decrcmenting a two-byte counter is a little less straightfbrward (Program 14.8). As before, the count start value is first secded through 'num' (lines 9264 to 9268). The decrement process begins at 'minus_one' by first loading the low byte of the count at 'block' into thc accumulator (line 9270). If this byte should be zero then thc decrement must take the high page byte into consideration and decrement this (line 9272). Finally, the low byte is decremented (line 9274).
As with the previous program, after the initial set up it is 'no_dcc' that must be called to perform the decrement as the BASIC demo illustrates.
Program 14.2
Procedure title : PROCtimerone_delav
Variables required : addr
Line numbers : 9060 to 9084
Length : 34 bytes
Zero page requirements : none
Registers changed : A, X, Y
Program 14.3
Procedure title : PROCtimertwo_delay
Variables required : addr
Line numbers : 9100 to 9124
Length : 34 bytes
Zero page requirements : none
Registers changed : A, X, Y
Program 14.4
Procedure title : PROCdelay
Variables required : addr
Line numbers : 9130 to 9179
Length : 48 bytes
Zero page requirements : none
Registers changed : none
Program 14.5
Procedure title : PROCpush_all
Variables required : addr
Line numbers : 9200 to 9211
Length : 6 bytes
Zero page requirements : none
Registers changed : none
Program 14.6
Procedure title : PROCpull_all
Variables required : addr
Line numbers : 9220 to 9231
Length : 6 bytes
Zero page requirements : none
Registcrs changed : A, X, Y
Odd One Out193
Program 14.7
Procedure title : PROCinc_count
Variables required : num, block, addr
Line numbers : 9240 to 9257
Length : 15 bytes
Zero page requirements : 2 bytes
Registers changed : A
Program 14.8
Procedure title : PROCdec_count
Variables required : num, block, addr
Line numbers : 9260 to 9278
Length : 17 bytes
Zero page requirements : 2 bytes
Registers changed : A