Contents
Section
2.1 Backing up 2.2 Disk version 2.3 ROM version
4.1 Line format and file handling 4.2 Line editing 4.3 Constants 4.4 Variables 4.5 Procedure and function names 4.6 Expressions 4.7 Commands 4.8 Statements and functions
9.1 Disk version 9.2 ROM version
12.1 CP/M System 12.2 ROM System
Appendix
© Arcom Control Systems Ltd. 1987
Manual | Software | Comments |
---|---|---|
v1 iss 2 | v1 iss 20 | 1987-02-12 first published in this format |
2010-11-24 Edited into HTML format |
This package is intended to provide an easy-to-use yet powerful environment for the development and testing of software for industrial control and measurement applications. It consists of two parts: one disk-based for CP/M systems, and the other EPROM based for use in stand-alone SCPUB systems. Both forms share the following features:
Ease of use. The user's program is entered, modified and tested in a manner that is very similar to conventional BASIC interpreters, yet the powerful optimising compiler generates pure machine code which runs up to 30 times faster.
Structuring. Many constructs are available to the user to assist in writing well-structured self-documenting programs: for example REPEAT-UNTIL and WHILE-WEND loops, long variable names, named procedures and functions, and local variables.
Multi-tasking. In addition to the main program, the user may specify a background task, to be executed in parallel, but completely independently. The current background task may be selected from a range of background tasks.
Input/output control. Facilities are provided to enable interfacing with a variety of input and output devices. The BASIC PRINT and INPUT commands can be re-directed to transfer data to and from various peripherals, including disk files on the CP/M version.
Stand-alone Operation. Compiled programs may be put in EPROM or on disk, for use in dedicated control or monitoring systems,
Hexadecimal numbers in this manual are denoted by a preceding '&' in keeping with the convention which this BASIC uses.
This manual assumes that the reader has a good working knowledge of the BASIC language: the author has not attempted to emulate the hundreds of introductory texts on the subject!
The compiler is provided in the following formats:
3.5 Inch disk
5.25 Inch disk
2 EPROMS type 27128
The two disk versions will work in any CP/M 2.2 or CP/M Plus system, but to gain full access to all features, an Arcom CP/M Plus system is recommended. The EPROMS are only suitable for use in the Arcom SCPUB processor board.
Do not use either of the distribution disks as your working disk. Copy the one of the distribution disks to another disk and use that. Disks can be copied using the Arcom CP/M Plus DISKFORM and DISKCOPY commands as described in the Arcom CP/M Plus manual.
Appendix A includes a routine to back up the distribution EPROMs when running the compiler in ROM. They can also be backed up from Arcom CP/M Plus using the EPROM command and an Arcom SEP EPROM programmer.
Each disk contains the following files:
AB80.COM The compiler RTD.HEX The run-time package for CP/M standalone programs RENUM.BAS Demonstration program: outline of a disk to disk BASIC Renumber utility.
No installation is necessary for the CP/M system: the compiler is run by typing
AB80
at the CP/M prompt, and pressing RETURN.
Program development will be slightly easier under CP/M than in ROM because disks can be used to store program on, rather than EPROMs and CP/M utilities can sometimes be used for debugging.
The two EPROMs are identified as:
Run-time Package (EPROM AB80A) Insert into IC 22 on SCPUB Stand-alone Compiler (EPROM AB80B) Insert into IC 23 on SCPUB
To use the compiler, both the EPROMs must be installed in the Arcom SCPUB with the links set for type 27128 EPROMs. The compiler requires two 8 kbyte RAM chips to be inserted into the SCPUB and the links set accordingly (see the SCPUB manual). A compiled program running on a SCPUB will probably only require 8K of RAM.
See Section 12 for information on connecting a terminal when running on a SCPUB.
If any of the disk commands are to be used on the ROM version, (for example LOAD, SAVE, COMP, OPENIN and OPENOUT) then a Arcom SEP EPROM programmer board must be installed in the system, since it is used to emulate a 'write-once' disk system.
Having connected the terminal as described in Section 12, switch the computer on.
The computer will print
Press RETURN
at 9600 baud. If your terminal is not set to this baud rate then you will not see this message. Press RETURN and the AB80 banner will appear and the compiler is ready for use. When running a compiled program the program will still go through this “autobaud” feature unless it is disabled as described in Section 12.
The purpose of this chapter is to provide a quick guided tour round the various features of the AB80 compiler. It is recommended that you have a 'live' system available to try out the examples given.
Assuming that you have installed either the CP/M or ROM versions of AB80, as described in the previous chapter, and have started it running, you should see the sign-on message as follows:
Multitasking BASIC Compiler for Z80 Version x.x (c) Reserved >
The '>' is a prompt for your input. So, to start with the most elementary of commands, type in:
PRINT 1+2
and press RETURN to execute the command. Not surprisingly, the answer '3' is returned, and the prompt re-appears. At this stage, it may appear that you are using (yet another) BASIC interpreter, since an immediate response was obtained to the command you typed. However, your command was, in fact, compiled to machine code before being executed. To show this, try the following:
PRINT "start": FOR i% = 0 TO 10000: NEXT i%: PRINT "finish"
When this command is executed, you will notice a short pause while the line is compiled, and another delay while the loop is executed: the latter is short, due to the very high speed of pure compiled machine code. The following points are also worthy of mention:
The variable used is 'in: the % sign indicates that it is an integer variable, as opposed to floating point (which would be 'i') or string (which would be 'i$'). The use of integers results in very much shorter, faster code. Since an integer is stored in 2 bytes of memory, the maximum range it can have is +32767 to -32768 (or 0 to &FFFF), but this is generally adequate for simple loops.
Whilst the above command has been written in a mixture of upper and lower case, it could have been typed in lower case only. All keywords (such as PRINT, FOR, NEXT) are automatically converted to uppercase. Variables are, however, case sensitive, so i% is not the same as I%. By convention, lower case is normally used for variables, to simplify recognition.
if you had mis-typed the above line, for example "PRINR" instead of PRINT, you would have seen the response:
PRINR (?) "start".......
The '(?)' indicates the place at which the compiler could no longer make sense of the line.
To continue the exploration of integers, try the command:
PRINT 5/3
The answer given, 1, appears to be completely wrong: what is going on? The compiler has (in the absence of any information to the contrary) done an integer calculation, since this represents the fastest, most efficient code. If a floating-point calculation is required, you have to insert a hint to that effect in your command. Any of the following would work:
PRINT 5/3.0 PRINT 5/3. PRINT 5./3 a = 5/3: PRINT a
The compiler assumes that an integer calculation is required, unless one or more of the variables or constants are floating-point. If the compiler is faced with a mixed expression, the above rule still holds, with the answer being converted to the required type for storage. Thus the expression:
i% = 5.0
is very wasteful, since the number is loaded in as floating-point, and converted to integer for storage. Before leaving the subject of variables, it is worth noting that long variable names up to 16 characters are permitted: thay must start with a letter, and may include letters, numbers and underscores (_). All characters are significant, as is their case. Thus:
count Count COUNT count_down count_down%
are all valid and completely separate variables. If you want to know the variables currently in use, use the VLIST command.
A program is created in exactly the same way as with a BASIC interpreter. Each line is typed in with a line number, the lines being stored within the compiler in ascending line-number order. An important difference is that the compiler performs a detailed syntax check on each line as it is entered. If there is an error, the line is printed out with the error marker (?)' at the point where the line ceased to make sense. The faulty line is still stored as usual. If you wish to correct the mistake immediately, it is not necessary to re-type the line completely, since a console line editor is provided. For more information on this, see Section 4.2.
Now try entering the program:
100 def bell 110 print chr$(7); 120 endproc
Lines 100 and 120 may be very dissimilar to the BASIC you are familiar with: that is because they represent a procedure definition. Procedures are a very useful way of encapsulating blocks of code, so that the inherent structure of a program can become clearer. To view the above program, type
LIST
and you should see
100 DEF bell 110 PRINT CHR$(7); 120 ENDPROC
Note that the keywords have been converted to uppercase, while the user-defined word 'bell' remains in lower case: this forms a useful check that you haven't mistakenly attempted to use a keyword for, say, a variable.
To run the above program, type the command
RUN
just as with a conventional BASIC interpreter. You should see a very brief flickering of line numbers as the compiler converts your program to machine code, but then you are simply returned to the I prompt. Why hasn't the procedure been executed? The answer lies in the fact that the DEF...ENDPROC is simply defining the procedure named 'bell, and you have not requested that the procedure be executed. One way to achieve this is to place a call to the procedure in the main body of the program, i.e. above the first procedure definition. For example:
10 bellThis will cause the procedure ‘bell’ to be executed every time the program is run. Alternatively, once the procedure has been compiled (by ‘running’ the definition), you can execute it from the keyboard by typing
bell
and the console should bleep as soon as you hit RETURN. Other points worth noting are:
in the above program, the body of the 'bell' procedure has been indented 2 spaces to the right. Whilst this is not essential, it does make the program easier to read. Similarly, it is worthwhile indenting the body of loops.
the compiler does not really require each procedure to be formally terminated: it will assume the end has been reached when another procedure definition is started, or the end of the program is encountered. However, the ENDPROC statement has been included to improve readability.
Now enter the following addition to the above program:
200 DEF delay(n%) 210 WHILE n% > 0 220 n% = n% -1 230 WEND 240 ENDPROC
Here we have a procedure producing a delay, proportional to the value specified when calling it. If we 'RUN' the program containing the definitions for 'bell and 'delay', we can then experiment with them at the keyboard. Try the command:
bell: delay(1000): bell: delay(5000): bell
and the function of the delay parameter should be apparent. Note that 'delay! will accept a floating point or integer value, but will convert that value to an integer internally (n%). Thus only a delay value between 0 and 32767 will achieve the desired effect, all negative values resulting in zero delay. Other points: WHILE..WEND: this construct may be unfamiliar. The expression after the WHILE' is tested, and looping continues as long as it is non-zero. Thus, we could have written instead:
WHILE 0% n% = n% - 1 WEND
which would have exactly the same effect for all positive values of n%
the variable 'n%' is local to the procedure. When the procedure is called, it's current value is saved, before it is set to the calling value. On return, the old value is restored. This can be demonstrated by executing the command:
n% = 7: delay(1000): PRINT n%
Despite being used within the procedure, the old value of 'n%', 7, is still retained and printed.
The parameters passed to a procedure are not confined to numbers, but can be strings as well. Consider the procedure:
300 DEF greet(name$, count%) 310 WHILE count% 320 PRINT "Hello"; name$; ". "; 330 count% = count% - 1 340 WEND 350 ENDPROC
Once compiled, you could enter at the keyboard
greet("Fred", 3)
and receive the reply
Hello Fred. Hello Fred. Hello Fred.
If you inadvertently specify a very large number of greetings, the endless stream of text can be interrupted by pressing control-C, or it can be paused by control-s, then restarted by pressing any key.
Before leaving procedures, it is worth explaining that a function is simply a procedure that returns an integer, floating-point, or string value. For example:
400 DEF divide(a, b) 410 = a/b
The only difference between this and a procedure is the use of '=' to return a value back to the calling program. Functions must be compiled before use in exactly the same way as procedures. Thus, having 'RUN' the function definition, it could be executed by typing
PRINT divide(2, 5)
and the answer '0.4' would be printed. It is important to note that this procedure returns a floating-point result, as indicated by its name, 'divide'. If we wanted it to return an integer, it would have to be named 'divide%. Thus the function
400 DEF divide%(a, b) 410 = a / b
would do a floating-point division on the two parameters, then return a (truncated) integer result. Similarly a function name ending in '$' must be used for a function returning a string.
At first sight it appears rather confusing that there is no obvious difference between procedures and functions, however this fact can be put to good use. You can safely call an integer or floating-point function, yet ignore the value it returns. For example:
400 DEF divide(a, b) 410 PRINT a; " divided by "; b; " is "; a/b 420 = a / b
This may either be used as a procedure:
divide(a, b)
and rely on its printed output, or the value it returns can be used as well:
c = divide(a, b)
if you inadvertently attempt to use the (non-existent) value returned by a procedure, no great harm will result, but the value is difficult to predict.
The value returned by a string function must always be used, otherwise it will permanently occupy string space thereafter.
The following section provides a detailed description of the AB80 language.
All program lines must have a line number in the range 1 to 32767, and consist of one or more statements, separated by colons. There must be at least one space after the line number.
Program or command lines for the AB80 compiler must be no more than 110 characters long, and should normally be terminated with a Carriage Return, Line Feed (CR, LF). The Line Feed may be omitted, since it is ignored within the compiler. Output lines from the compiler (for example SAVE, LIST) may be up to 126 characters long, and will be terminated with CR, LF. All files must be terminated by the control-z end of file marker.
Files in this format should be compatible with most CP/M text editors, and word processors in the non-document mode.
The file description applies to both disk files (CP/M system) and EPROM files (ROM system). Transfer between the two is possible using an EPROM programmer as source files are stored in ASCII on both media.
Filenames for the disk system may be in upper or lower case: they will be translated to upper case before use. The drive identifier (for example A:) may be omitted if the current drive is to be used. The file extension may also be omitted when using LOAD, SAVE and COMP, since it will default to "BAS' for source files, and "HEX' for object files.
The filenames on a EPROM system are used to identify the type of EPROM to be read/programmed, and the start location within the EPROM. The only EPROMS supported are 2764 and 27128 types, and they are programmed at 21 volts using the fast programming algorithm. The filenames must consist of "2764" or "27128" (no other characters), with an optional extension containing the EPROM start address in hex divided by 16. For example,
LOAD "27128.080"
will load a program from location &800 in a 27128 EPROM. Note that the commands act immediately, so the EPROM must already be inserted in the socket on the Arcom SEP. If the extension is omitted, a value of 0 will be assumed. The switch on the SEP EPROM programmer must be set to the correct position for 2764/27128 EPROMS (i.e. set on').
Using the OPENIN and OPENOUT instructions, serial access of data files is possible. These are normally text files, terminated by control-Z. Binary files may be handled using GET# and PUT#, since these access a single byte, and no character conversion (or control-Z detection) is performed.
A maximum of one input file and one output file may be open at any one time. On a ROM system, the OPENIN and OPENOUT instructions cause the Arcom SEP programmer to be powered up, to emulate a 'write-once' disk. The programmer will be powered down when a CLOSE# instruction is executed. An EPROM should not be opened for input and output simultaneously.
At any time during the entry of source lines, and when entering data to the INPUT command the following control characters may be used from the console. Control keys C,S,Q,P can be used during program execution unless breaks are disabled.
Note that when a character is typed at the console, it is inserted at the current location in the edit buffer, and all other characters (up to the end of line) are moved up. This operation is transparent to the user, and is only of use when editing the last console line (or source lines when using the EDIT command). The line may be redisplayed using control-B, then the cursor positioned so that any offending characters may be deleted using the BACKSPACE or DELETE keys, or inserted by just typing them at the appropriate location. Pressing RETURN at any point enters the displayed line. After editing it will be necessary to press control-B twice so as to redisplay the the entire edited line before pressing RETURN.
To economise on memory, the keyboard input buffer is shared with the file input buffer, so immediately after any disk or EPROM input operations, the keyboard input buffer will contain spurious data. This will only be noticeable if an attempt is made to recall the last keyboard entry, when this data will become visible. Due to extraneous control characters, it may not always be possible to clear the offending screen display: however, the keyboard buffer itself will always be cleared if control-X then RETURN are pressed.
There are 4 types of constant:
Integer: whole numbers between -32768 and 32767, without a decimal point, 2222 for example.
Hexadecimal: hexadecimal numbers between 0 and FFFF, prefixed by an ampersand '&', for example &f3a2, &0287.
Floating point: numbers with a decimal point or an exponent, for example 1.0, 123.45e6. The allowable range is 1e38 to 1e-38.
String: a sequence of alphanumeric characters enclosed in double quotation marks, for example "String Constant".
Note that if the compiler encounters an over-size integer constant (for example 40000) it will be stored as a floating-point constant, and compiled in as such.
Variable names may be up to 16 characters long, must start with an alphabetic character, and may contain alphabetic, numeric, or underscore '_' characters. All characters are significant, as is their case. Variable names should not start with a keyword, though names identical to keywords may occasionally be accepted by the compiler when the context is clear. This practice is not recommended since it can easily lead to confusion.
Variable names may not contain embedded spaces, nor must there be any spaces between the name and opening bracket of an array description.
There are 3 data types for variables.
Integer: can have values between -32768 and 32767
Float: can have values between 1e38 and 1e-38
String: can be of any length (up to the maximum string space available) and can contain any printing or control characters.
The last character of the variable name indicates its type: ''%' for integer, '$' for string, otherwise float. Variables with the same base name, but different type, are treated as different variables (for example 'var' is completely separate from 'var%').
Array variables must be dimensioned before use (see the DIM statement). The theoretical maximum size of any dimension is 32767, and there is no effective limit on the number of dimensions.
The memory requirements for variables in a stand-alone program are:
Integer: 2 bytes Float: 4 bytes String: 2 bytes, and (4+length) bytes in the string storage area.
When the compiler is being used interactively, the memory requirements for variables are harder to predict, since the variable storage area is also used to hold the various name tables.
The string storage space must very substantially exceed the maximum requirements of the strings contained within it, since there is always a certain amount of wastage in dynamically allocated storage.
All variables are cleared when a program is compiled, or when a standalone program is run.
Additional procedures and functions may be defined by the user (see the DEF command). The restrictions on the names are identical to those for variable and array names. Care must be taken to avoid using the same name for both a procedure and a variable, since it is difficult to predict which will take precedence in a given expression,
The only difference between procedure and function definitions is that functions return a value, and therefore must be of a specific data type. The type of a function is indicated by the last character of its name, in an identical manner to a variable.
An expression consists of one or more variables, constants and functions linked by assignment, logical, comparison or arithmetic operators.
The order of precedence of operators is:
HIGHEST Multiplication, division, modulus *, /, MOD Negation, addition, subtraction, bit -, +, -, BIT Comparison <, <=, -, >, >=, <> Logical NOT, AND, OR, XOR LOWEST Assignment =
The operators have the following type restrictions:
* | operates on integers, floats or strings, and gives a result of the same type. |
* / - | operate on either integers or floats, and give results of the same type. |
< <= = > >= <> | operate on integers, floats or strings, and give an integer result (1 = true, 0 = false). |
MOD, NOT, AND, OR, XOR, BIT | operate on integers, and return an integer result. |
String inequalities indicate which string comes sooner in the alphabet, with earlier words being less than later ones. The string can contain any characters as the BASIC only looks at the ASCII value. Note that all lower case letters are considered greater than upper case letters. For example:
"aardvark" < "zz top" "aardvark" > "ZZ TOP" "aardvarks" > "aardvark"
The MOD operator performs modulo arithmetic, i.e. returns the remainder of the division of two operands. The BIT Operator returns the value (0 or 1) of the bit number specified by the second operator. For example:
a% = b% BIT n%
returns the value of bit n% in b%, where n% is between 0 and 15.
When an expression with a mixture of integers and floats is encountered, the compiler will perform all ari thmetic in floating-point, and insert the necessary type conversions. However, if an expression consists of only integers, integer arithmetic will be performed. For example, all the following will be performed in integer mode:
PRINT 2/3 PRINT 1/a% b% = a% / 3 AND 5
since a%, b% and all constants are integers. However the following will be performed as floating-point:
PRINT 2/3. PRINT 1/a b = a / 3 AND 5
In the last example, the result of a/3 has to be temporarily truncated to an integer, in order to perform the AND.
When expressions are nested, each will have an indi vidual data type assigned, for example in array referencing:
3 / b%(10 * a)
In this expression, the multiplication is performed in floating point (since 'al is a float), the result being truncated to form the array dimension. However, the division is integer, since there are no floats in that expression.
In applications where speed or code size is critical, it is important to avoid the use of floating-point calculation wherever possible.
AB80 is equipped with the following commands, which can only be executed directly (not in programs). Each command line may only contain a single command.
CLEAR re-allocates program, stack and string space. The user's program, all variables and procedure declarations are cleared.
CLEAR has three optional parameters, which may be used to change the string and stack space allocation, and the address of the bottom of usable RAM. For example:
CLEAR 1000, 2000, 88000 This sets the string space to 1000 bytes, the stack space to 2000 bytes, and the bottom of usable memory to 88000. If any parameter is omitted the default value will be used. For example:
CLEAR 2000, , &8000
By this command, the RAM available on a ROM system may be expanded to 32k, provided a suitable RAM card has been installed on the STEbus.
COMP compiles the BASIC program in memory, but does not run it. The object code is either put in memory, saved in EPROM or saved to an Intel format HEX disk file. The optional parameters are:
filename: name of disk or EPROM output file rtp: start address of run-time package obj: start address of object code top: top address of free RAM
If the COMP command is entered with no arguments, the program in memory is compiled to memory like RUN, but is not executed. Execution can then be initiated by running named procedures from the keyboard, or by a direct GOTO command. Note that a program must be re-COMPiled after any changes are made to the program.
If a filename is specified, then compilation will be to disk (when running under CP/M), or EPROM (in ROM). On a CP/M system, an Intel format HEX file is generated, which can be loaded into an EPROM programmer, or converted into an executable disk file for stand-alone operation. If no extension is specified, AB80 will append .HEX to the filename.
When compiling for execution later (stand-alone program) the COMP command requires 4 parameters. See Section 9 for information on producing stand-alone programs. For example:
COMP "test", &100, &3800, &B000
This will produce a file called 'TEST.HEX' on the default line, to operate with the run-time package at &100, the start of object code at &3800, and the top of available RAM at &B000. The stack size and string space size are those currently in use at the time of compilation (see the 'CLEAR' command).
For a ROM system, the command might be:
COMP "2764", &400, &4000, &0000
This produces the output on a 2764 EPROM, starting at EPROM address 0 (actual address &4000 when put into IC23 on the SCPUB), with the run-time package at &400, the program at &4000, and the top of usable RAM at &D000.
To produce exactly the same code on a disk system, as a HEX file, the command would be:
COMP "test", &400, &4000, &0000
Any or all of the numeric parameters may be omitted (or specified as 0), in which case default values will be assumed, based on the system in use at compile-time. For example:
COMP "test", ,&4800
If the file extension is omitted, 'HEX' is assumed.
During compilation, a running indication is kept of the line number currently being compiled. At any time, compilation may be aborted by control-C.
If the COMP command is outputting a disk file, it dumps and clears the object code buffer at the start of every procedure/function definition, and does not al locate any actual space for variable storage. This should enable the compilation of a much larger program than that permitted with RUN, especially if it is split into many procedures.
For further information on run-time parameters, see the MLIST command, and Section 9.
WARNING:
If COMP is used with parameters, it is probable that the stand-alone code generated is incompatible with the compiler memory environment. It is strongly recommended that a CLEAR command be used immediately afterwards, otherwise accidental use of variables or object code will cause compiler corruption.
EDIT lnum finds the first line with a number greater than or equal to 'lnum', prints it, and enters it into the keyboard buffer, so that the console editing controls may be used on it. For example:
EDIT 100
will cause the first line numbered 100 or greater to be made available for editing. See Section 4.2 for the editing commands available.
LIST lists the source program at the console. At any time, the listing can be paused by control-S (until another key is pressed) or aborted by control-C. Control-P can be used to echo the listing to the printer.
There are two optional parameters, which must be decimal or hexadecimal numbers specifying the start and finish line numbers. For example:
LIST 100 will list only line 100, LIST 100, will list from line 100 onward, LIST ,100 will list from the first line to line 100 LIST 100,200 will list from line 100 to 200.
If a LIST command is performed having first set TRACE ON, then the listing will include all the tokens indicating the type of the expressions. For example:
10 PRINT (int) 2/3
where the '(int)' marker is always present in the internally stored text, but not normally visible to the programmer. This feature is of use in determining the type of complex expressions.
LOAD is used to recall a BASIC source file from disk (CP/M version) or EPROM (ROM version) and to load it into the compiler. A syntax check is performed on the file, and any errors are flagged. A running display is kept of the current line number being processed. The input may be halted at any time by use of control-c.
See Section 4.1 for more information on EPROM and disk filenames.
Examples:
LOAD "TEST" LOAD "prime.bas" LOAD "b:scan" LOAD "2764" LOAD "27128.0C0"
MLIST lists the addresses of the compiler work areas to assist the development of standalone programs. Is is normally used immediately after a RUN command, in order to give an overview of the program currently under development. The values printed are the start addresses of the:
Source program
Object code
Free memory
Variable storage
Stack space
String storage
Compiler workspace
From this, the following parameters can be deduced:
b-a: Source length c-b: Object code length d-c: Free memory e-d: Size of current variable storage (includes the compiler name lists for variables and procedures) f-e: Size of allocated stack space g-f: Size of allocated string space
NEW is functionally identical to CLEAR. The source program and variables are cleared from memory.
OLD tries to recover a source program in memory. After a CLEAR or NEW command, the source program is not fully erased from memory, the end-of-program marker has only been moved to the start. The OLD command attempts to recover the source program by shifting this marker back. However, if any BASIC program lines have been entered in the interim, this attempt will fail, and no further lines will be recovered.
RUN compiles and runs the BASIC program in memory. Any errors encountered are flagged. If the compilation is error free, the resulting object code is then run.
During compilation, a running indication is kept of the line number currently being compiled. At any time, compilation may be aborted by use of the 'Break' key (control-C).
SAVE saves a BASIC source file to disk (CP/M version) or EPROM (ROM version). See Section 4.1 for more information on EPROM and disk filenames.
Examples:
SAVE "TEST" SAVE "prime.bas" SAVE "b:scan" SAVE "2764.100" SAVE "27128"
If a disk error occurs then an error message is printed.
SYS exits to the operating system. This command will cause an immediate exit to the operating system. Any work in progress is lost, unless it has been saved.
VLIST lists the current variables, procedures and functions. Since BASIC permits variables to be defined by simply referring to them, it is important to keep track of those currently in use. This command will list all those that have been defined in the last program compiled, and those created subsequently by keyboard commands. Also, all currently defined procedures and functions are listed.
The address where the value of a variable is stored is printed alongside the variable name. The address of a procedure is printed by the procedure name. The value of a variable is not printed because after a cross-compilation variable storage is not initialised.
This command can be used to detect common errors in referencing variables e.g. confusion over upper/lower case causing several versions of the same variable to be generated.
The following statements and functions may all be used directly from the keyboard, or in program lines. The keywords may be entered in upper or lower case, though they will always be listed in upper case.
ABS(expr) returns the absolute (that is non-negative) value of an integer or floating-point expression. For example:
ABS(-10)
returns a value of 10.
ASC(string) returns the ASCII value of the first character in a string. For example:
i% = ASC("abc")
will set i% to 97 decimal, the code for "a".
BMOVE is a very flexible block move command. Data can be moved within the current address space or to or from STEbus memory. All bank switching is done automatically.
BMOVE moves 'len' bytes starting from address isrceaddr' in bank 'srcebank' to address 'destaddri in bank 'destbank'. The block of data moved can straddle a 64K boundary.
A bank of memory is a 64K page. Thus the STEbus address accessed by a bank number srcebank and an address srceaddr is
( 65536 * srcebank ) + srceaddr.
For example:
BMOVE &8000, 0, &C000, 2, &1000
will move a block 1000H bytes long from bank 0 address 8000H (that is STEbus address 08000H) to bank 2 address C000H (that is STEbus address 20000H).
A bank number of -1 indicates that the memory currently in the 280 address space is to be used, without bank switching. When both bank numbers are -1, an intelligent block move is performed; that is the method of operation is chosen so as not to corrupt data when the source and destination areas overlap.
The Arcom SCPUA can only access 192K of STEbus memory, so when running on the SCPUA a bank number should not be greater than 3. Bank 0 on the SCPUA is onboard RAM.
Due to the memory map of CP/M on the Arcom SCPUB it is not possible to access the top 16K of any 64K bank on the STEbus. An attempt to do so will result in an access to the on-board RAM.
These bank numbers do not necessarily correspond to CP/M Plus bank numbers. Warning. Only use memory not used by CP/M. (Any memory that need not be inserted on the STEbus for CP/M to run properly is not used by CP/M).
BREAK OFF and BREAK ON are run-time commands to disable and re-enable the monitoring of the console for break (control-c) or pause (control-s) characters.
If BREAK OFF is executed, the console input channel will not be scanned at all, enabling its use for other purposes. Also, it will be impossible for the user to accidentally abort the program. The console input scan will be re-enabled when BREAK ON is executed, or automatically when control is returned to the compiler.
If a non-control character is fetched as a result of the keyboard scan, it is held in a single-character buffer so that it is available for any subsequent INPUT, INCH etc.
Enabling and disabling breaks will affect both tasks in a multi-tasking program.
EXCLUDE SCAN resembles BREAK OFF except that it is a compile-time (rather than run-time) instruction, and it eliminates all scan calls in the code that follows it. See EXCLUDE SCAN for more information.
CALL calls a machine code routine given by the expression 'addrl, and if the optional Tregs' parameter is used, presets the processor registers to the values in the integer array pointed to by 'regs'. On return, the array (if specified) is re-loaded with the returning register values. For example
CALL 5, VARPTR(R%)
This calls a routine at address 5, (CP/M BDOS vector) with the required register values given in an integer array 'R%, which must have been previously dimensioned using:
DIM R%(3)
The locations in the array are used as follows:
0 - Accumulator A 1 - Registers BC 2 - Registers DE 3 - Registers HL
Thus to call location 5 with the BC registers set to 7, the instructions would be:
DIM R%(3) R%(1) = 7: CALL 5, VARPTR(R%)
If, for example, the value returned in the accumulator is to be printed, this can subsequently be done by:
PRINT R%(0)
Note: the use of the VARPTR function in the CALL command to pass a pointer to the register array. It is important that this method be used, otherwise program corruption will occur on return. The array need not be called R%.
CHR$(expr) returns a single-character string containing the ASCII code given by 'expr', which may be any expression returning a value from 0 to 255 decimal. For example:
a$ = CHR$(i%+&30)
CLOSE# closes a file which has been opened for input or output. For example:
o% = OPENOUT ("file") ... CLOSE# 0%
A file may remain open for the whole duration of a program, since it is unaffected by any I/O to other channels. Thus, for example, in a logging application the output disk file may remain open for a long time, with bytes being occasionally written to it using 'PUT#'. AB80 automatically buffers the bytes into sectors before sending them out to disk.
It is important to close an output file after use, as this clears out any data held in the disk buffer, and writes out the end-of-file marker.
DEEK(addr) returns an integer value corresponding to the 2 bytes at address addr and addr+1, which is an expression evaluating to an address between 0 and 65535 decimal. The byte at the lower address is the less significant byte. For example:
i % = DEEK (a%+&10)
DEF is used at the head of each user-defined procedure or function. It serves to delimit the body of the definition from the preceding section of the program, to identify the procedure/function parameters and the type of the value returned by a function.
The DEF command must be the first statement on the source line, and should not be followed by any other executable statements. When the procedure/function is called, the format is:
name (arg1, arg2, ... )
With this call, the current values of the variables represented by 'param1', 'param2' etc are saved, and the values from the arguments 'arg1', 'arg2' etc are substituted. On return from the procedure/function, the variables represented by 'param1', 'param2' etc are restored to their previous value. Since no new variable locations are created, the code produced is recursive but not re-entrant.
If a procedure or function has no parameters, the brackets may be omitted in the definition and in the call. For example:
DEF name name
At compile-time, the compiler will attempt to match the type and number of the calling arguments to the type and number of parameters, inserting integer-float or float-integer conversions where necessary.
It is desirable, but not essential, that a procedure be terminated by an ENDPROC or RETURN statement. By default, the start of a new procedure definition, or the end of the source program, will be assumed to be the end of the current procedure.
Functions must be terminated by one or more statements giving the value to be returned. These are of the form
= expr
indicating that the function must return at this point with the value of expr, which may be an integer, float or string depending on the type of the function.
At the start of each procedure or function definition, the compiler checks to ensure that all loops (for example FOR, WHILE, REPEAT) and CASE statements have been correctly terminated: if not, compilation stops with an appropriate error message.
Procedures or functions need not necessarily be defined before they are called, however any main program statements must come before the first procedure definition, since the compiler inserts a 'return' instruction in front of every definition.
DIM is used to dimension one or more arrays. There are no restrictions on the number of dimensions an array may have, and the maxi mum size of each dimension is only constrained by the amount of available memory.
When using RUN, the compiler checks that sufficient memory is available to hold the array, however the COMP command can not do this, so the programmer must check for correct memory utilisation.
When an array is referenced, the format is:
arr1 (expr1, expr2, ...)
where the expressions must evaluate to values less than or equal to the corresponding dimensions. This is checked at run-time, and an error message is generated if an attempt is made to access non-existent elements. For example, if an array is dimensioned with
DIM a(10)
then the elements a(0) to a(10) may be accessed.
DOKE sets the two bytes at 'addr' to the given value. Both 'addr' and 'val' are expressions evaluating to values between 0 and 65535 decimal, for example
DOKE a%+&20, &10
As is standard with the Z80 processor, the low-order byte is stored in the lower address.
ENDPROC is normally used to mark the end of a procedure definition. As far as the compiler is comcerned, ENDPROC and RETURN are identical: both simply generate a machine-code return instruction. A procedure may have multiple ENDPROC or RETURN statements, or none at all. In the latter case, a return instruction is generated when the next DEF instruction is encountered, or when the end of the program is reached.
EXCLUDE SCAN and INCLUDE SCAN are compile-time directives used to bracket sections of source in which no 'scan' calls are required. By default, the compiler will insert a 'scan' call at the start of each source line. This call permits:
Identification of current line number for errors
Checking console for 'break' or print pause
Checking of stack for overflow
Execution of 'ON SCAN' call if required.
Tracing of program execution if required
For example:
EXCLUDE SCAN DEF proc1 ... DEF proc2 ... INCLUDE SCAN DEF proc3 ...
Procedures 1 and 2 will run faster due to the absence of 'scan' calls, but any user break' (control-C) or print pause (control-s) will not be acknowledged until another Section of program is executed (by, say, one of them calling procedure 3). In the event of an error ocurring in them, the line number displayed will be that corresponding to the last executed 'scan'. Note that if all 'scan' calls are to be excluded from a procedure, the 'EXCLUDE SCAN' directive must precede the DEF' statement, as in the example above.
EXCLUDE SCAN will have no effect when used as an immediate command.
If a non-control character is fetched as a result of the keyboard scan, it is held in a single-character buffer so that it is available for any subsequent INPUT, INCH etc.
EXT DEF is used to inform the compiler about the type and location of external procedures or functions, that is those not being compiled in the current program. This instruction is principally of use in referring to previously compiled sections of code. For example, if a previous procedure is currently in the system at &9000, this procedure may be defined using
EXT &9000 DEF test(i%)
assuming it takes a single integer argument. The EXT instruction does not itself cause any code to be generated, but enables the procedure to be referenced by the name 'test', just as if it had been defined in the current source.
The address parameter may only be a decimal or hexadecimal number. Each EXT instruction may only define one procedure or function, and must be on a line by itself.
See Section 8 for more information.
FOR NEXT is used to construct a loop that will execute a series of statements a pre-determined number of times. Start, finish and increment are expressions that are evaluated only once. For speed reasons, the variable should normally be an integer, in which case it can be omitted from the NEXT statement. For example:
FOR i% = 0 TO 100 NEXT
However, a float variable will be accepted, providing it is explicitly mentioned in the NEXT statement. For example:
FOR a = -1 to -11 STEP -1 NEXT a
The STEP value may be omitted, in which case a value of +1 is assumed.
If the step is +ve, the loop will terminate when the variable is greater than or equal to the TO value. If -ve, it will terminate when the variable is less than or equal to the TO value. Since this comparison is performed at the NEXT command, the loop will always be executed at least once.
The FOR loop may be unsuitable for manipulating memory pointers, since it assumes all integers are signed. Thus:
FOR i% = &7f00 TO &8010 ... NEXT
will loop only once, since in signed arithmetic this is:
FOR i% = 32512 TO -32752 ...
GOTO statements should not be used to jump out of FOR loops, since the loop information will be retained on the stack. However, if a RETURN or ENDPROC statement is executed with loop information still on the stack, that information will automatically be deleted.
The maximum number of FOR, WHILE or REPEAT loops that can be nested at compile-time is 16.
If speed is very critical, it will be quicker to use a WHILE or REPEAT loop.
GET# returns a character obtained from the input channel specified by the expression 'chan'. If this corresponds to the channel number given by a previous OPENIN instruction, then the character will be obtained from disk. For example:
i% = OPENIN("file"): a% = GET# i%
The characters are not modified on input: none of the usual character conversions is performed. If no character is available on the input channel (for example because the end of the disk file has been reached), then a value of -1 is returned. If GET# is being used to input a text file, then the program must stop input when the control-Z (ASCII 26) end-of-file marker is returned.
GET# need not necessarily be used with a disk file; the channel number can be any one that is valid for the present system. For example:
0 - console 2 - CP/M 'AUX' device
CP/M 2.2 systems cannot poll the 'AUX' device, so on these systems GET# on that channel will cause the system to wait for a character.
If the input is to be redirected via the ON IN command, then GET## must be used. For example:
a% = GET##
GOTO lnum is used to make an unconditional jump to a given line number. 'lnum' must be a decimal number between 1 and 32767 which corresponds to a line number within the current program, function or procedure.
For program clarity GOTO instructions should be avoided wherever possible.
GOSUB is used to call a user subroutine at the given line number, which must be between 1 and 32767, and within the current program, function or procedure.
If structured programming methods are being used, it is recommended that all GOSUBS are converted to procedure calls without parameters: program clarity is enhanced, and there is no run-time speed penalty.
HEX$ returns a string containing the hexadecimal representation of the expression 'expr', with no additional leading or trailing spaces. For example:
PRINT HEX$(255)
will print 'FF'.
IF THEN ELSE is used to conditionally control the execution of program statements. The condition may be any expression, which is tested for being true (non-zero) or false (zero).
In the first form above, the statement list is only executed if the condition expression evaluates to a non-zero result.
In the second form above, if the condition expression is non-zero statement list1 is executed. If the condition expression is zero then statement list 2 is executed.
In all the above cases, the statement on the next line is executed after any list of statements, unless a GOTO statement is executed in the statement list. A statement list can contain any number of statements.
THEN may optionally be replaced by a GOTO. For example:
IF i% GOTO 120 ELSE GOTO 130
Generally, the need for these GOTOs can be avoided by the use of the WHEN command as a multi-line IF (see WHEN command).
INCH waits until a character is available at the console, then returns its ASCII value. For example:
100 PRINT "Y or N?"; 110 c% = INCH: IF c% = &59 OR c% = &79 THEN...
INP returns the byte at STEbus I/O address corresponding to 'expr', 'expr' must be between 0 and 4095. For example:
a% = INP (i%+&20)
INPUT will cause the prompt string to be displayed on the console, with a question mark. The user then types a series of decimal integer, hexadecimal, floating-point, or string constants separated by commas, and terminated by a Carriage Return. The variables are then set to the corresponding input values: if there are too few values, the user will be re-prompted with "?" to enter the remaining ones. If there are too many, the excess will be ignored. If any of the values contain spurious trailing characters, these will be ignored: if the result remains unidentifiable, the corresponding variable will be set to 0.
For example, if the statement
INPUT "Input data"; a, b%, c$
is executed, and the user enters
2.3m, x4, "zzz"
then 'a' will be set to 2.3, "b% to 0, and 'c$' to 'zzz". When an integer variable is specified, any non-integer characters will be rejected, for example 1E2 would be input as 1. This may be circumvented by specifying a float variable in the input statement, then assigning the result to an integer variable for further processing.
The '?' prompt may be suppressed by omitting the semicolon after the prompt:
INPUT "prompt" var1, var2, ...
The whole input line (including commas) may be fetched into a single variable by the use of:
INPUT LINE "prompt"; var $
INPUT# is used in a similar manner as the INPUT command, except that the input is obtained from the channel given by the integer expression 'chan'. When combined with the OPEN IN command, this enables data to be input from disk. For example:
i% = OPENIN("t.bas") INPUT LINE# i%; a$ WHILE a$ > "" PRINT a$; INPUT LINE# i%; a$ WEND
This program fragment reads in the file "t.bas" from disk, and prints it out until the end of the file is reached.
INPUT# need not necessarily be used with a disk file, the channel number can be any one that is valid for the present system. See Section 12 for information on channels.
INPUT## or INPUT LINE## can be used if the input is redirected via the ON IN command, For example:
100 ON IN getchar 110 INPUT##; n 120 DEF getchar(i%) . .
INSTR searches for the first occurence of string2 in string1, and returns the position at which a match is found. If there is no match, 0 is returned. For example:
pos% = INSTR(line$, *****)
INTERRUPTS can be disabled during time-critical or non-reentrant sections of code by use of INTERRUPT OFF and re-enabled using INTERRUPT ON. Interrupts are enabled on entry to the compiler.
Note that CP/M on the SCPUB uses interrupts for console input. Therefore when running a program that disables interrupts it is necessary to re-enable them when character input is required and before the program returns to the user.
KBD Returns the ASCII code of any character available at the console, or 0 if no character. Any character obtained will not be echoed back to the console.
Line Feed (ASCII 10) characters will be ignored (returning 0 as if no character had been received), and Carriage Return (ASCII 13) will be translated to Line Feed. If 'break' is enabled, then control-C will stop program execution, and control-s will cause any subsequent printing to be paused.
If an ON SCAN procedure has been specified, then it will be executed once, if no character was available from the console.
If it is necessary to wait for character input, a REPEAT loop should be used:
REPEAT char% = KBD: UNTIL char%
LEFT$ returns a string containing the number of characters given by 'exp' from the leftmost side of 'string'. For example:
100 a$ = "abcde" 110 PRINT LEFT$(a$, 2)
will print "ab". The expression must be a positive integer: if it is greater than the overall string length, the whole string is returned.
LEN returns the length of 'string'. For example:
100 a$ = "asd" 110 PRINT LEN(a$)
will print "3".
LET var = expr When an assignment statement is used, it may optionally be prefaced by the LET keyword. For example:
LET a = b * c
LOCAL is used to create variables which are local to the current procedure or function. At run-time, the LOCAL statement causes the value of the variables to be saved, and then restored on return from the procedure or function. For example:
LOCAL a, b%, c$ causes the variables 'a', 'b', 'c$1 to be treated as local. LOCAL will not accept array variables. Since no new variable locations are created, the code produced is recursive but not re-entrant.
Procedure or function parameters are automatically locall, but any other variables must be specifically identified as such, if required.
MID$ will return a string of 'n' characters from the middle of the string, starting at the character position given by 'pos' For example:
PRINT MID$("qwertyu", 2, 3)
will print 'ert'.
The in' parameter may be omitted, in which case all the characters from the given position to the end of the string will be returned.
ON ERROR is used to specify a user-defined procedure to be called in place of the usual error handler when a run-time error occurs. For example:
ON ERROR fault
The procedure may have a single integer parameter which will be set with the error number. For example:
10 ON ERROR fault . . 100 EXCLUDE SCAN 110 DEF fault(errnum%) . .
The ON ERROR command is generally only used in standalone systems, where there may not be a console to display any error conditions. It enables the error to be notified by an alternative means, for example a warning light. Most errors are fatal and the error handler should exit using a STOP instruction.
It is recommended that the error handler have scan calls excluded, as in the example above, otherwise endless recursion may occur.
ON IN is used to redirect character input via a user-defined function. Whenever input is requested from channel 31, (as selected by INPUT## or GET##) each character will be redirected to the user function selected by ON IN. For example:
ON IN char_in% ... INPUT##, a$ ... DEF char in% REPEAT c% = KBD: UNTIL c% IF c% >= &61 THEN 0% = 0%-&20 = c%
In this example, the user function char_in% repeatedly polls the console until a character is available. When one is obtained, it is tested to see if it is lower case, and if so, it is changed to upper. The final "= c%" returns the character to the INPUT## instruction, where it will be treated as if it had been received directly from the keyboard. Note that the ON IN function MUST be of type integer (hence the '%'), and usually it should not echo any characters fetched (hence the use of KBD).
The ON IN function must not contain any INPUT## or GET## instructions, otherwise endless recursion will occur.
ON OUT procname ON OUT is used to redirect character output via a user-defined procedure. Whenever output is sent to channel 31, (as selected by PRINT## or PUT##) each character will be redirected to the user procedure selected by ON OUT. The procedure must have a single integer parameter to receive the character. For example:
ON OUT char_out ... PRINT##, a$ ... DEF char out(c%) IF c% = &20 THEN RETURN PRINT CHR$(c%);
In the above example, each character of 'a$' is redirected to the procedure 'char_out', where it is only printed if it is not a space.
The ON OUT procedure must not contain any PRINT## or PUT## instructions, otherwise endless recursion will occur.
ON INTERRUPT is used to specify a user-defined procedure that will be executed whenever an interrupt is vectored into the appropriate jump address in the runtime package. For example:
ON INTERRUPT int_handler
There are various constraints on the use of interrupt procedures, do to the bulk of object code being non-reentrant. See Section 6 for more information.
ON SCAN procnane ON SCAN is used to call a specified user-defined procedure at the beginning of every source line, and during keyboard input. This ensures that the procedure will be called frequently, though not necessarily at regular intervals. For example:
ON SCAN check status
will call the procedure 'check_status' at frequent intervals. Operation of the main program will be suspended until 'check_status' returns.
During an ON SCAN call, no other ON SCAN calls are performed, otherwise endless recursion would be set up.
A program may contain multiple ON SCAN statements. The latest one to be executed will decide the procedure to be scanned.
The ON SCAN statement may also be used with SWAP TASK to enable full multitasking. For example:
ON SCAN SWAP TASK
See SWAP TASK for more information.
OPEN# executes the initialisation code for a channel. It is generally used only on a ROM-based system. For example:
OPEN# 2
OPENIN opens the file for input, and returns the associated channel number for use in INPUT# or GET#. For example:
i% = OPENIN("test.dat") INPUT# i%, n
Only one input file may be open at any one time: an attempt to open a second one will cause the first to be closed.
Since only one input file may be opened at any one time, the same channel number (32) is always returned.
See Section 4.1 for more information on EPROM and disk filenames. If an error of any sort occurs in a disk operation the program will print an error message or enter the ON ERROR routine.
OPENOUT opens the file for output, and returns the associated channel number for use in PRINT# or PUT#. For example:
o% = OPENOUT("test.dat") PRINT# o%, n
Only one output file may be open at any one time: an attempt to open a second one will cause the first to be closed. Since only one output file may be opened at any one time, the same channel number (33) is always returned.
If you wish to append to an existing file the only way is to open the file for input, open a new file for output, copy the data into the new file and then write more data on the end of the new file.
See Section 4.1 for more information on EPROM and disk filenames.
OUT will truncate 'expr' to an integer, and output its low-order byte to the port address, which must have a value between 0 and 4095. For example:
OUT a%+&40, b%
PEEK returns an integer value corresponding to the byte at 'addrl, which is an expression evaluating to an address between 0 and 65535 decimal. For example
i% = PEEK (a%+&10)
POKE sets the byte at 'addr’ to the given value. addr' is an expression evaluating to a value between 0 and 65535 decimal and val evaluates to a value between 0 and 255.
POKE a%+&20, &10
POS returns the position of the cursor. Thus if the cursor is on the left hand side of the screen POS returns 0.
POS enables a screen display to be laid out easily.
PRINT takes a series of integer, float or string expressions, and outputs their values to the console. The expressions must be separated by delimiters, which indicate where printing should resume:
',' resume at the next 8-character column
';' continue where previous expression finished
The complete output string is normally terminated by Carriage Return and Line Feed: this may be suppressed by ending the PRINT statement with "," For example
PRINT i%; " and", b/3;
PRINT may be used without arguments in order to move the cursor down one line. Numbers are either printed in integer or floating-point mode, depending on the type of expression. Where there is ambiguity, integer will be assumed. Numbers are printed without any leading or trailing spaces.
Characters within strings will be subject to the following type conversions on output:
Line Feed (ASCII 10) => Carriage Return, Line Feed Backspace (ASCII 8) => Backspace, Space, Backspace
The Line Feed character is generally used within the compiler to generate the end-of-line sequence, leaving Carriage Return to be used only when it is necessary to over-type the current line.
The print pause (control-S) facility is not provided automatically by the PRINT command, since it requires the end-of-line scan to detect when a key has been pressed. Thus if a print loop is confined to one line, for example:
FOR i% = 0 TO 1000: PRINT i%: NEXT
it will not be possible to pause (or abort by control-c) the printed output while the program was in the loop.
PRINT# is used in a similar manner as the PRINT command, except that the output is directed to the channel given by the integer expression chan'. When combined with the OPENOUT command, this enables data to be sent to disk. For example:
o% = OPENOUT("t.dat") PRINT# o%, n;
PRINT# and PUT# commands may be intermixed to output the full range of characters to the file.
PRINT# can also be used to send output to any serial channel. For example:
PRINT #1, "This output goes to the printer";
If the output is to be redirected via the ON OUT command, then PRINT## must be used. For example:
PRINT##, n
PUT# chan, char PUT# outputs a character to the channel specified by the expression 'chan'. If this corresponds to the channel number given by a previous OPENOUT instruction, then the character will be output to disk. For example:
i% = OPENOUT ("file") PUT# i%, ASC("?")
The characters are not modified on output: none of the usual character conversions is performed. Hence, PUT# may be used to generate a binary (non-text) file.
If the output is to be redirected via the ON OUT command, then PUT## must be used. For example:
PUT##, n
This command enables string, integer or floating-point data to be embedded in the program, to be read at run-time. The data statements have the form
DATA item1, item2, ...
where the data items may be integers (decimal or hex), floats, or strings (with or without quotes). In any given block of data statements, there may be no other instructions included.
Prior to reading the first data statement, a RESTORE statement to the starting line number must be issued. For example:
10 RESTORE 100 20 READ i% ... 100 DATA 11, 22, &c0 ...
The types of the variables in the READ statement(s) must match the types of the data.
Subsequent RESTORE statements may be issued to move the read pointer to other data blocks, but a READ or RESTORE should not attempt to reference a data block outside the current procedure, function or main program.
As with procedure definitions, when a block of data is compiled the first instruction generated is a RET instruction to prevent the program executing data.
REM precedes any non-executable statement which is used to annotate the source program. The rest of the source line, including any punctuation marks, will be ignored by the compiler. For example:
REM This is a comment.
REPEAT UNTIL is a loop which will continue until the expression, when truncated to an integer, is true (non-zero). For example:
REPEAT PRINT a: a = a + 0.1 UNTIL a > 0
The expression need not contain a comparison. For example:
REPEAT char% = KBD: UNTIL char%
which will repeat until char% is non-zero.
Since the expression is only tested at the end of the loop, the statements between REPEAT and UNTIL will always be executed at least once.
A REPEAT UNTIL loop should have only one UNTIL clause to exit from the loop.
RETURN may be used at any point in a procedure, when control is to be returned to the calling program or procedure. Any local variables will be automatically restored, and any FOR loops within the procedure which are still active will be deleted.
The abbreviated form RET may be used for program entry, but the full keyword will always be printed out.
If a RETURN is executed when in the main program, control will be returned to the AB80 compiler or to the operating system, depending on whether the compiler or a stand-alone CP/M program is running.
returns a string containing the number of characters given by 'exp' from the rightmost side of 'string'. For example:
PRINT RIGHT$("abcde", 2)
will print 'de'. The expression must be a positive integer: if it is greater than the overall string length, the whole string is returned.
STOP causes the current program to be aborted, and control is returned to the compiler or the operating system, depending on whether the compiler or a standalone program is running. On a ROMed program the program will reinitialise and re-start.
STR$ returns a string containing the decimal representation of the integer expression 'exp', with no additional leading or trailing spaces. For example:
a$ = STR$(&ff)
will put the string "255" into 'a$'.
If an alternate task has been specified using the TASK statement, then SWAP TASK will switch between whichever is the current task, and the suspended task. Execution will continue from where it was stopped in the suspended task.
This command is normally used with ON SCAN, so that two tasks can be executed concurrently (multi-tasking). For example:
TASK check input ON SCAN SWAP TASK
Note that the SWAP TASK statement must be preceded by a TASK definition.
TASK specifies a user-defined procedure that is to be the alternate task in multi-tasking. For example:
TASK clock
In order to permit apparently simultaneous operation of two different program segments, it is necessary to maintain two separate machine-code stacks. This is achieved by splitting the current stack in two, and allocating the lower half to the alternate task. If the upper half of the stack is already full, this will be flagged as an error.
Multiple TASK statements may be included in a program, in which the last one executed will take precedence. This feature can be used to dynamically switch between a variety of alternate tasks.
This statement causes any diagnostic trace (selected with TRACE ON) to be disabled. TRACE OFF is automatically executed if an error is encountered, or if control-cis typed at the console.
TRACE ON and TRACE OFF may be used as direct commands, or as program statements, temporarily inserted for debug purposes. TRACE ON will cause the line numbers of all executed lines to be printed out, to show program flow.
The line number print is performed by the start-of-line 'scan', so will be disabled on sections of the program which EXCLUDE SCAN. If ON SCAN is active, the line number printout will occur after the 'scan' call, just before the line is executed.
If a LIST command is performed with the trace enabled, then the listing will include all the 'invisible tokens indicating the type of the expressions. For example:
PRINT (int) 2/3
where the '(int)' marker is always present in the internally stored text, but not normally visible to the programmer. This feature is of use in determining the type of complex expressions.
VAL returns the integer value of a decimal or hexadecimal numeric string. For example:
i% = VAL("$10")
will set 'i%' to 16.
VARPTR returns the address where the user variable, procedure or function given by 'var_name' is stored. Where an array name is specified, the address of the first location is returned. For example:
DIM a(10) i% = VARPTR(a)
will set 'i% to the start address of array 'a'. If a string variable is specified, the address of the string pointer is returned, thus
DEEK (VARPTR(a$))
will return a pointer to the appropriate string space entry (or possibly 0 if a null string).
Pointers to variable storage should be used with care.
WHEN provides a means of selecting one of several options, depending on the value of an expression. For example:
WHEN a%-b% IS < 0 THEN PRINT "Negative" IS = 0% THEN PRINT "Correct value" IS > 10 THEN PRINT "Too large" ELSE PRINT "Nearly right" ENDWHEN
The expression immediately following WHEN is evaluated, and truncated to an integer. Each time an IS statement is encountered, the value is inserted into the comparison, so the first is statement above effectively becomes:
IF a%-b% < 0 THEN ...
If the comparison fails, the next is statement is tried. When one passes, all the statements up to the next IS statement are executed, then control passes to the statement after ENDWHEN. Thus, only one of the IS conditions can ever be executed. If none of the conditions is satisfied, the optional ELSE condition will be executed instead.
Each IS or ELSE statement may cover several source lines. For example:
IS > 10 THEN PRINT "Too "; PRINT "large" ELSE PRINT "Nearly "; PRINT "right"
The WHEN expression may be followed by other statements which will be executed prior to evaluating the first IS. For example:
WHEN a%(%): i% = i% + 1 IS < 0...
The WHEN statement may effectively be used as a multi-line IF statement, avoiding the use of a GOTO. Note that THEN and ELSE must be followed by at least one statement on the same line, or by a colon, for example:
WHEN a% IS > 0 THEN: ... ELSE ... ... ENDWHEN
A WHILE loop will continue while the expression, when truncated to an integer, is true (non-zero). For example:
WHILE a < 0 PRINT a: a = a + 0.1 WEND
The expression need not contain a comparison. For example:
WHILE i%: i% = i%-1: WEND
which will repeat until i% is zero.
Since the expression is tested at the start of the loop, if it starts off being false, then the body of the loop is not executed at all.
The multi-tasking facility of ABB0 can best be explained by reference to a demonstration program.
10 REM square root (with irritating multi tasked bleep) 20 TASK bleeps 30 ON SCAN SWAP TASK 40 WHILE 1 50 INPUT "Number(1-100)", n 60 ans = n : 70 REPEAT 80 error = 1 - ans * ans / n 90 ans = ans + error 100 UNTIL error < 1e-5 AND error > -1e-5 110 PRINT "Square root of "; n; " is "; ans 120 WEND 190 REM -- 200 DEF bleeps: REM endlessly! 210 WHILE 1 220 delay(200): PRINT CHR$(7); 230 WEND 240 ENDPROC 250 DEF delay(n%) 260 WHILE n% > 0 270 0% = n% - 1: WEND 280 ENDPROC
The main program from lines 40 to 120 prompts the user to input a number between 1 and 100, then finds its square root using a simple iterative algorithm. Lines 40 and 120 contain a never-ending loop ('WHILE 1' will always be true), so that on completion the user is re-prompted for another number.
The program contains another task which loops endlessly, namely 'bleeps'. This sends a endless succession of 'bell' characters to the console. It uses a procedure called 'delay' to provide a short pause between each bleep.
To demonstrate that these are two completely separate parts of the program, they could be compiled (using COMP without parameters) then executed directly by "GOTO 40" (for main prog) or "bleeps" (for procedure). Note that both will loop endlessly, and have to be aborted with control-c.
To execute both in parallel, line 20 contains the statement
TASK bleeps
which defines which procedure is to be the alternate one. After this definition, the first time SWAP TASK is executed, the current program is suspended and the alternate procedure is started. The next SWAP TASK causes the alternate program to be suspended, and the main program to be resumed, and so on.
To run both tasks effectively in parallel, a steady stream of instructions must be executed. It is very convenient to swap tasks at the start of each source line, since it is a relatively quiet' time. Also, tasks must be swapped during keyboard input, otherwise the system would grind to a halt while the user is typing. This is achieved by line 30 which makes task swapping occur on every 'scan', that is the start of every source line (when looking for control-c, control-s etc.) and during keyboard input. The net result in the above program is that you get a continual (very irritating) bleep while the main program is running. The bleep will continue, irrespective of whatever is going on in the main program. For example, if a value of 0.1 is entered, the square root algorithm does not converge, and loops endlessly, but the multi-tasked bleep carries on! However, pressing control-C will terminate both tasks immediately. If the background task were to terminate by a normal RETURN instruction, then it is automatically disabled, and must be re-enabled by another TASK instruction. If the main program terminates by executing a RETURN, then the background task is automatically hal ted. If program performance suffers due to too frequent task-swapping, then parts of the code can be bracketed with EXCLUDE SCAN and INCLUDE SCAN instructions, which will cause them to run much faster. If the main task and the background task are both competing for keyboard (or any other) input, then any characters will be randomly distributed between the tasks. The INPUT command should only be used in one task. If the same procedure or function is used simultaneously in the main program and the background task, there is a risk that local variables will be corrupted, due to the non-reentrant nature of the code. Such procedures should have scans excluded, to avoid this conflict. whilst it is theoretically possible to run an interrupt-driven multi-tasking program by using great care is needed in implementing this due to the non-reentrant nature of the AB80 object code and run-time package (see Section 6). In practice, scandriven task-swapping is more than adequate for most purposes. NOTE: that if two tasks both use PRINT then ^s should not be used to pause printing. Any interrupt that is to be handled by AB80 must be vectored through to the appropriate jump 6 bytes ater the start of the run-time package (406h for ROM system, 106h for disk system). Thereafter, the registers are saved, and control is passed to the procedure specified by the ON INTERRUPT instruction. On return, interrupts are re-enabled. Unfortunately, although AB80 procedures can be used recursively (they can call themselves), the bulk of code generated by AB80 and in the run-time package is non-reentrant (it may not be used simultaneously by 2 different tasks). If the same code is executed in the main program and interrupting program, corruption of variables (and, worse, string space) will occur. For safety, an interrupting procedure should only use: Alternatively, an INTERRUPT OFF instruction may be executed prior to entering an offending procedure, with an INTERRUPT ON after it returns. In the initialisation code of the run-time package, the Z80 processor is set to interrupt mode 2, and the 'I' register is set to &FE. In both ROM version, and the CP/M version (running under Arcom CP/M Plus), the RAM from &FE80 to &FEFF is available for a user interrupt vector table. To handle interrupts the following operations must be performed. All interrupt handler routines must have 'scans' excluded. The EXCLUDE SCAN command must come BEFORE the DEF statement. An example of using the CTC on the SCPUB as a clock is given in Appendix A. The following information is provided as a general guide to those wishing to interface Z80 machine-code routines to AB80 object code. It assumes a detailed knowledge of Z80 assembly language programming. The start of an AB80 object code file is as follows: The top-of-stack value is the top-of-mem value given by the user in the COMP command, minus the string and var space required. The 'jwarmup' routine shifts the stack to the approriate place, and returns with two pointers to the exit routine on the new stack. By inspection of the 'jwarmup' address, it is possible to determine the run-time package address, since the run-time package begins with: The jump table in the main program contains entries for all user procedures or functions, in the order they were defined. When a procedure or function is called, all the arguments are first pushed onto the stack in the order they are specified, and then popped off after return. For example, to call a procedure defined as the code would be This calling convention is the same as that used by the C/80 C compiler. If a function returns a float, then it is returned in BCDE: integers and string pointers are returned in HL. A string pointer points to an entry in the string space consisting of a two-byte length, followed by the string bytes. The length is negative if the string is a temporary, that is it should be deleted (length set to 0) after use. The string pointer itself may be zero, which indicates a null string (no space al located): all string pointers should be automatically cleared to this state on power-up. The jump table at the start of a segment of compiled BASIC can be used when calling a function defined and compiled separately. A jump to the first function declared is put 5 bytes after the start of the segment, the second 8 bytes from the start and the third 11 bytes after the start etc. For example, suppose that part of a program was compiled to run at 8000H. If the main part of the program was compiled separately and wished to call the first function called TEST of the first part this could be done by the following piece of code. When generating standalone programs with the COMP command, the appropriate address parameters have to be specified. To assist in choosing the correct values, the standalone run-time memory maps are given below (not to scale): SWAP TASK
30 ON SCAN SWAP TASK
ON INTERRUPT SWAP TASK
6. Interrupts
7. Machine-code Interfacing
LD B,C ;dummy instructions for identification
LD B,D
JP start ; jump to start of main prog
JP proc1 ; jump table for all procs and funcs
JP proc2
...
start:
LD HL, top_of_stack
PUSH AL
CALL jwarmup ; set stack top
POP BC
LD HL,heapsiz
PUSH HL
LD HL,varsiz
PUSH HL
LD HL,stacksiz
PUSH HL
CALL set_hvs ; set heap, var space and stack
POP BC
POP BC
POP BC
... ;start of user main program
RET
proc1: ;first procedure/function
...
RET
proc2: ;second procedure/function
...
JP jcold ; jump to cold start
jwarmup:
JP warmup ; jump to 'warmup'
JP jint ; jump to interrupt handler
DEF proc(a%, b, c$)
LD HL,int_val
PUSH HL
LD BC,flt_hi
LD DE,flt_lo
PUSH BC
PUSH DE
LD HL,str_ptr
PUSH HL
CALL proc
POP HL
POP HL
POP AL
POP HL
9 REM Declare TEST as first function of segment at &8000
10 EXT &8005 DEF TEST(i%)
.
.
100 a = TEST(7)
.
.
Section 8. Memory Maps
Disk Version:
0000 | CP/M workspace |
0100 | Run-time package |
3600 | Run-time workspace |
3800 | User program |
FREE MEM | |
Stack space | |
User variable space | |
String space | |
FFFF | CP/M workspace |
0000 | Hardware drivers |
0400 | Run-time package |
4000 | User program |
8000 | FREE MEM |
Stack space | |
User variable space | |
String space | |
F400 | Run-time workspace |
FE00 | Hardware workspace |
On a ROM system the FREEMEM area includes the area 08000H to OBFFFH, in which the SCPUB makes bus accesses. If you wish to use this area a SCRAM board can be put into the system (with a backplane) to allow 16K of RAM or ROM to be added to extend either the program or data space available. Extra RAM must be indicated using the CLEAR command before compilation.
Several different programs may be resident at one time, by suitable manipulation of the program start address and top-of-mem address. The programs can then call each other's procedures by having them declared as external using EXT DEF, and called the same as the resident procedures. However, note that only the first program executed will determine which stack and string space is used, thereafter all the others will use this space as well. Also, the programs and var spaces must be correctly nested, so that the main program can clear the variables for the others on start-up. For example:
User program 1 | |
User program 2 | |
FREE MEM | |
Stack space 1 | |
User var space 1 | |
String space 1 | UNUSED STACK SPACE 2 MAY BE OVERLAYED BY STRING SPACE 1 |
User var space 2 | |
String space 2 | (UNUSED) |
If program 1 is executed on start-up, it will automatically clear the variables for program 2, since it clears up to the bottom of the run-time workspace. It is very important that string variables are cleared by the run-time package before any use, otherwise their pointers may cause memory corruption.
When RUNning large programs users may find that they get an Out of Memory error. This means that there is not sufficient space to hold the compiler, the source code and the object code in memory. The solution to this is to compile the program to disk, which allows much larger programs to be compiled.
Programs compiled to disk and converted to binary form can be run as normal CP/M utilities. In order to make the utilities general it is advisable to specify a very low RAM top when compiling so that the utility will run on a CP/M systems with a small TPA (program area), &8000 is suitable, but you may have to increase this if you have a large program.
An example of producing a stand-alone program for CP/M is given in Appendix B. The runtime package address should be &0100 for CP/M. The object code address should be &3800 for a program that is to be executed directly. If the program is to be executed indirectly (for example by EXT..DEF calls), then this can be anywhere within the available memory.
The top-of-RAM value chosen is very dependent on the target system CP/M size, To determine the maximum value, run AB80 on the target system, execute the MLIST command, and note the "Buffers" address. This will be the value used by default for the top-of-RAM address. Note that if you use the default top-of-RAM address then the program will not run on CP/M systems with a smaller TPA.
If only a filename is specified in the COMP command, then all the other parameters will default to sensible values for the current CP/M system. The parameters only really need to be used when compiling multiple programs to work together, or a program to run a different system to the one currently in use (for example compiling on a banked CP/M system, to run on a non-banked one).
Having created a HEX file with the COMP command, this now has to be combined with the run-time package and converted to a binary (executable) file. This is achieved by exiting from AB80, and using the debug utility SID version 3, supplied with all CP/M plus systems.
Assuming all the AB80 files are on drive B:, load the run-time package into SID using
SID B:RTD.HEX
You should get a response similar to:
NEXT MSZE PC END 36F7 36F7 0100 BBFF
Then read in your compiled program (called, say, "test") using the command
RB:TEST.HEX
Again you should be prompted
NEXT MSZE PC END 39EC 39EC 0100 BBFF
The precise values printed will depend on the length of program. Any additional programs can now be loaded by the same means. Then write the complete file out using
WB:TEST.COM
and SID should respond
0072h record(s) written
the precise number again depending on program size. Now exit from SID using control-C, and you have an normal executable file TEST.COM on drive B.
If AB80 is being used on a CP/M 2.2 system where SID version 3 is unavailable, then the DDT debug utility can be used as follows:
DDT B:RTD.HEX IB:TEST.COM R
Now you have to exit DDT using control-Cc, and use the SAVE command to write the appropriate number of 256-byte blocks to disk. The number of blocks is deduced from the program size as reported by DDT: if it is 5000 hex bytes, then 79 blocks (50 hex minus 1) must be saved using
SAVE 79 TEST.COM
The ROM system can generate standalone programs in EPROM to run on an SCPUB processor board. The run-time package address has to be at 400 hex. In order to be automatically started, the object code address has to be 4000 hex, unless the program is to be called from another program using EXT DEF. The top of RAM address should not be more than &F400 so as not to clash with the run-time workspace. If multiple programs are being run, then their workspaces must not clash (see Section 8 on how to arrange this). The runtime package does not need to be combined with the compiled code because the runtime package is already provided in a separate EPROM.
When compiling on a ROM based system the code is output directly to EPROM if an appropriate filename is specified (see Section 4.1). Appendix C gives an example of cross-compiling a program on CP/M to run stand-alone on a SCPUB.
For information on disabling the autobaud feature see Section 12.2
The error numbers and their possible causes are as follows:
DIVIDE BY 0:
An integer or floating-point division by 0.
CHANNEL I/O:
The read or write to a channel failed. Probably due to an incorrect channel number being specified, or an error writing to disk/EPROM, perhaps because it is full.
NOT COMPILED YET:
An attempt has been made to execute an uncompiled program.
FOR-NEXT MISMATCH:
A NEXT was encountered without a matching FOR, perhaps due to the variable not being specified correctly in the NEXT (in floating-point loops the variable must be explicitly declared in the NEXT).
NO FILE:
The named file couldn't be found, or, for a ROM system, the named EPROM type and start address are incorrect.
DIM TOO LARGE:
An attempt has been made to use an array dimension outside its defined range.
VAR LOCATION:
A standalone program has detected a conflict between the memory allocated for its stack and variables. It will have to be recompiled.
TOO MANY LOOPS:
The maximum permitted number of nested WHEN statements and loops has been exceeded.
OUT OF DATA:
A READ instruction has come to the end of the available data (all data must be in a contiguous block).
MEM FULL:
At compile-time, there is insufficient free memory between the top of the object code and the bottom of the variable buffer. The CLEAR command may be used to re-allocate the RAM.
STACK O/FLOW:
At compile-time, this may be caused by the attempt to analyse an expression with very large nesting of brackets. At run-time, this is probably due to a procedure/function calling itself endlessly. A larger stack space can be allocated using the CLEAR command.
STRING O/FLOW:
There is insufficient string space to accommodate the current string expression. The maximum string space required is many times larger than the total number of characters stored in it, due to the requirements for storage of string transients, and the inevitable inefficiencies of any dynamic storage method. More string space can be allocated using the CLEAR command.
SOURCE CORRUPT:
The linked-list format used by the compiler to store the source program has become corrupted by the user program. Since there is possibly other corruption as well, the compile session should be aborted and re-started. Check the source program for invalid use of POKE etc., or invalid machine-code calls.
NAMES CORRUPT:
The compiler name list which holds the variable names and storage has become corrupt, probably for the same reasons as error 20 above.
STRINGS CORRUPT:
A run-time string variable pointer has been altered SO that it does not point to the string space. See error 20 above.
COMPILER:
The compiler has detected corruption of its own code. See error 20 above.
LINE PTR:
When attempting to match a line number to an object code address, the compiler has found the latter points to an invalid address, for example one outside the current program segment being compiled. Check the destination of any GOTOs or GOSUBs.
UNTERMINATED LOOP/WHEN:
The compiler has detected a FOR, WHILE, REPEAT or WHEN which has not been terminated before the end of the current program or procedure.
The following notes are intended as a guide to converting programs from other dialects of BASIC. Since there is no standard form of BASIC, such conversions will inevitably involve a certain amount of trial and error. The numbering of the sections below corresponds to the previous sections in the manual, where further information can be obtained.
Maximum line length is 126 characters. Lines are stored and recalled as standard ASCII, not in tokenised form. Spaces within the line are significant, and are sometimes necessary to delimit keywords.
Leading '&' is used to denote hexadecimal numbers. Integers constants may not be greater than +32767: if they are, they will be promoted to floating-point.
Character constants have a different meaning to many BASICs. On input line feed is ignored and carriage return is translated to line feed. On output line feed is translated to carriage return, line feed.
Variable names may be up to 16 characters long, with all characters and case significant. Trailing '%' indicates integer type. All arrays must be explicitly dimensioned before use. There is no practical limit to string length. When porting programs users may find that they have to add a '%' to many variable names to make them integers.
Do not have to start with PROC or FN.
The compiler makes assumptions about the type (integer or float) of an expression, based on the types of the variables and constants. All-integer expressions may be evaluated differently to the method an interpreter would use.
Parameters vary between BASICS.
Not implemented, due to difficulty of re-creating stack at time of user interruption.
GOTO can be used instead to restart part of the main program.
Requires a ASCII file.
Parameters vary between BASICS.
Can not be used to run from a line number.
Saves as a ASCII file.
Used to return to operating system: other BASICS use different commands to do this.
Since a compiler is very much larger than the corresponding interpreter, several useful functions have had to be omitted. The criteria for retention of a function is that it is essential for well-structured industrial control programs, and it can not readily be emulated by a user-defined function.
No trigonometric functions implemented (no room!)
Data statements must be in contiguous blocks: any other intervening statements will be interpreted as data block terminators.
DEF is used for general-purpose procedure and function
definitions, so syntax may differ from other BASICS. DEF must be the first (and only) statement on the line.
All arrays must be declared explicitly (no implicit dimensioning).
Not available. STOP is functionally equivalent.
Not available. Error number is passed as a parameter to the ON ERROR function.
The NEXT variable may only be omitted if integer. Only one variable may be specified for each NEXT statement.
Only 'IF... THEN statement' or 'IF... GOTO linenumber' forms are permitted.
Will not re-prompt user if too little data is entered (zeros any remaining variables). Ignores any erroneous data (sets corresponding variable to 0). Line editor is available during user input.
Not available. Can be achieved by assigning expression to an integer variable. For example:
var = INT(a/b)
should be converted to
i% = a/b : var = i%
Does not echo any character fetched.
May not be used on the left-hand side of an expression.
see FOR.
Not available. The WHEN command may be used instead.
"ON ERROR func' may be used instead, but program execution may not be resumed.
Not available.
Not available, since stack is not only used to hold return addresses.
Must be preceded by a RESTORE statement.
Must have a line number argument.
Not available.
Not available: MLIST may be used instead.
TAB() is not provided by the BASIC. It can be simulated by the following code.
1000 DEF TAB$(n%) 1010 LOCAL a$ 1020 LOCAL i% 1030 a$ = "" 1040 i% = n% - POS() 1050 WHILE i% > 0 1060 a$ = a$ + " " 1070 WEND 1080 =a$ 7090 RETURN
BASICS that store programs in non-ASCII form can be converted to ASCII form using the CP/M Plus PUT command to send a listing of the program to a disk file. See your CP/M Plus manual for details of PUT.
Both CP/M and ROM systems can use ON IN and ON OUT to direct character I/O to a driver written in BASIC. INPUT##, GET##, PUT## and PRINT## all vector through these routines.
On a CP/M system the following channels available are:
Channel Input Output 0 CONIN: CONOUT: (usually the console, channel A on SCPUA or SCPUB) 1 LSTOUT: (usually a printer, channel B on SCPUA or SCPUB) 2 AUXIN: AUXOUT: (the auxilliary device, also channel B on SCPUA or SCPUB) 31 Accessed using GET##, PUT##, PRINT## etc., and vectored through ON IN and ON OUT functions. 32 File read channel. 33 File write channel.
Input from channel 1 is not allowed. Files must be opened for read and write using OPENIN and OPENOUT. A file must not be opened for input and output simultaneously. Random access of files is not possible. Appending to files can only be achieved by the following method.
open file for input open file2 for output copy all of data from file1 to file2 write additional data to file2 close file1 and file2 file2 now holds the data in file with more data appended.
Any disk errors are directed through the ON ERROR routine if used.
When running in ROM the following channels are available:
Channel Device 0 Channel A of DART on SCPUB 1 Channel B of DART on SCPUB 2 - 7 Vectored through jump tables described below to null devices 31 Accessed using GET##, PUT##, PRINT## etc., and vectored through ON IN and ON OUT functions. 32 EPROM read channel. 33 EPROM write channel.
An EPROM cannot be opened for input and output simultaneously.
When running the compiler in ROM you must have a serial terminal connected as described below, with an optional printer. When running compiled programs in ROM on a SCPUB the serial channels are still designed to be connected to a terminal and printer as described below, but you may wish to use them for something else.
Channel A of the DART should be connected to a VDT with an RS232 interface running at one of the following baud rates: 50, 75, 110, 134.5, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600. The terminal is connected via connector PL3, which on the bottom left of the board (looking at the component side with the 64-way bus connector on the right). Pin 1 is marked and the connector is numbered as for a 20-way ribbon cable. Signals come out from the Arcom SCPUB on pin 1, and are received by the Arcom SCPUB on pin 2. Pin 5 is the ground. There is no handshaking on the terminal input or output. The data format is 8-bit ASCII with the most significant bit masked, no parity and two stop bits. Communication should be full-duplex, character mode. Any standard RS232 VDU or video display terminal should be able to cope with this (eg. the RS VDT 101).
Channel B of the DART is intended for connecting to a printer. This channel is initialised to run at 1200 baud, 8-bit data, two stop bits and no parity. The auto-enable mode of the DART is switched on. This mode causes the CTS (clear to send) input to act as an enable for the transmitter and the DCD (data carrier detect) input to act as an enable for the receiver,
Most printers use a handshake line to stop the computer sending out characters too fast. For these printers it is necessary to connect the output from the DART (PL3 pin 10) to the input of the printer (usually pin 3 on the 25-way Dtype connector, but check your printer manual), and CTS (PL3 pin 7) to the handshake output of the printer, CTS must be high to enable data flow. For the RS printers with standard jumpering and many other printers, connect this to RTS from the printer, pin 4 of the 25-way D-type connector, with non-standard jumpering and on some other printers DTR, pin 20, may be the handshake output, but since this varies check the printer. Section 12 gives details on how routine CIST1 can change the baud rate of the SCPUB printer port.
On a ROM-based system two channels are provided using the jump table structure described below. There is provision for six more.
Channel Device 0 Channel A of DART ON SCPUB 1 Channel B of DART on SCPUB
The low level character I/O routines are accessed through jump tables in the EPROM. The jump tables start at location 80H (128 decimal). The structure of the jump table is as follows:
; ; Jump table ; 0080H ; ; character I/O routines ; JPCITO: JP CITO ; initialise JPCISO: JP CISO ; input status JPCIO: JP CIO ; input a character JPCOSO: JP COSO ; output status JPCOO: JP COO ; output a character
The jumps for the character I/O routines point at additional jump tables, allowing the possibility of more I/O channels being supported if the software is updated. These jump tables look like:
CITO: JP CINI0 ; initialise device 0 JP CINI1 ; initialise device 1 ; . . . . ; . . . . JP CINI7 ; CISO: JP CIST0 ; i/p status of device 0 JP CIST1 ; i/p status of device 1 ; . . . . ; . . . . CIO: JP CIN0 ; input from device 0 JP CIN1 ; input from device 1 ; COSO: JP COST0 ; o/p status of device 0 JP COST1 ; o/p status of device 1 ; COO: JP COUT0 ; output to device 0 JP COUT1 ; output to device 1
Any future changes to the software will not change the structure of the jump tables, so that by indexing via the addresses in the jump table starting at location 80H, user programs will not need modification if the software is updated. This jump table structure is also used by the machine code monitor for the Arcom SCPUB so that the software runs in the same environment.
Initialise a character device. This is called with the Z80 register A = baud rate. The allowed values for baud rate are from 1 to 14 as defined above. No register other than A is changed.
Input status of a character device. This returns A = 0 and flag Z set if no character is available, or A = FFH and flag z reset if a character is available. No other register is changed.
Input a character. This returns in A the next available character from this device. The character is an 8-bit value, except for device 0, for which the character is assumed to be ASCII and the most-significant bit is forced to zero. No register other than A is changed.
Output status of a character device. This returns A = 0 if the transmitter is still busy, or A = FFH if the transmitter is ready to accept another character. No other register is changed.
Output a character. This outputs the 8-bit value in A. No register is changed.
On a ROM system, the start of AB80A, the runtime package EPROM, is organised as follows:
Address 0 of the EPROM contains a jump to the initialisation code which then jumps to BASIC.
The area from 06H to 3FH (6 to 63 decimal) is left free for user patches, for example for jumps from the restart addresses.
I/O routine name | Jump table name | The jump table starting address is stored at hex: | |
---|---|---|---|
3-device system (AB80 v1.20) | 16-device system (AB80 v1.18) | ||
CINI0, CINI1 | CITO | 81 | C3 |
CIST0, CIST1 | CISO | 84 | F3 |
CIN0, CIN1 | CIO | 87 | 123 |
COST0, COST1 | COSO | 8A | 153 |
COUT0, COUT1 | COO | 8D | 183 |
The area from 40H to 5FH (64 to 95 decimal) is reserved for data used by the I/O routines. At present the locations that are used are as follows:
Address 40H (64 decimal); this byte specifies the default baud rate for the console, which is the baud rate that will be used if the auto-baud is disabled. The allowed values (in decimal) are: Address 41H (65 decimal): this byte specifies whether the auto-baud feature is to be enabled. Set to 0 to disable (so the baud rate is fixed at the value specified by location 40H), set to FFH to enable. The runtime EPROM is supplied with auto-baud enabled. Address 44H (95 decimal); this byte sets the baud rate of DART channel B (used for the printer). The allowed values are as for the console port described above (address 40H). The runtime EPROM is supplied with this set to 8, corresponding to 1200 baud.
Value Baud
1 = 50
2 = 75
3 = 110
4 = 134.5
5 = 150
6 = 300
7 = 600
8 = 1200
9 = 1800
10 = 2400
11 = 3600
12 = 4800
13 = 7200
14 = 9600
Appendix A. Example Programs
Prime Number Program
1 REM Prime number benchmark (BYTE Jan 83).
2 REM -------------------------------------
5 EXCLUDE SCAN
10 DIM flags%(4000)
20 PRINT "10 Iterations"
30 FOR m% = 1 TO 10
40 count% = 0
50 FOR i% = 0 TO 3999
10 REM Interrupt test using SCPUB CTC 20 ON INTERRUPT int_handler 30 DOKE &FE86, 8106 : REM Set up interrupt vector. Change to &406 for ROM 40 CTC = 8: CTC3 = 11 50 OUT CTC, 880 : REM Interrupt vector 60 OUT CTC3, &A7 : REM Set up CTC for timer mode and interrupts 70 OUT CTC3, 156 : REM Time constant 80 WHILE 1 90 PRINT time%; CHR$(&D);: WEND 100 EXCLUDE SCAN 200 DEF int handler 210 x% = x%+1 220 IF x% > 99 THEN % = 0: time% = time%+1 290 REM Before exiting to the system the CTC 295 REM must be stopped by calling this procedure 300 DEF reset ctc : REM Call this to stop interrupts 310 OUT CTC3, 3
Each number in the Fibonacci series is the sum of the previous two. For example, the series starts 1 2 3 5 8 13 21.
1 REM This program uses two tasks. 2 REM FIB1 makes B% leap frog' over A% when it is smaller 3 REM FIB2 Does the opposite. 4 REM The net result is that the two tasks wait for 5 REM their desired condition to occur and then perform 6 REM an action. The task then 'sleeps' until the 7 REM condition occurs again. 10 A% = 1 20 B% = 2 30 TASK FIB2 40 ON SCAN SWAP TASK 50 FIB1 100 DEF FIB1 110 IF A% > B% THEN B% = B% + A% : PRINT "1: % 115 IF A% < 0 OR B% < 0 THEN RETURN 120 GOTO 110 200 DEF FIB2 210 IF B% > A% THEN A% = A% + B% : PRINT "2: ";A% 220 GOTO 210
100 PRINT "EPROM Backing up Program." 110 PRINT "Ensure that you have an SEP board inserted." 140 copy(0) 150 PRINT "Remove the EPROM and label it AB80A." 160 copy(&4000) 170 PRINT "Remove the EPROM and label it AB80B." 180 PRINT "Backing up finished"
1000 DEF copy(addr%) : REM Copy &4000 bytes from address addr% 1010 PRINT "Insert blank 27128 EPROM into SEP and press RETURN"; 1020 INPUT "" A$ 1030 x% = OPENOUT ("27128") 1040 FOR i% = 0 TO &3FFF 1050 PUT# x%, PEEK (addr%+i%) 1060 NEXT i% 1070 CLOSE# x% 1080 RETURN
10 FOR i% = 0 to 7 20 PRINT "The factorial of ";i%;" is "; factorial(i%) 30 NEXT i% 100 DEF factorial (N%) 110 IF N% <= 0 THEN = 1 120 = N% * factorial(N%-1)
10 REM B80 demonstration: outline of a disk-to-disk Basic Renumber program. 20 REM will only renumber line num and one GOTO/GOSUB/RESTORE per line! 30 REM User may adapt/extend this program (e.g. to include start line num) 40 REM (c) J.P.Bentham 1986 50 REM 60 DIM lnum%(500) 70 INPUT "Source file"; s$ 80 INPUT "Output file"; o$ 90 INPUT "offset, increment (default 10, 10); oset%, incr% 100 IF oset% <= 0 THEN oset% = 10 110 IF incr% <= 0 THEN incr% = 10 120 i% = OPEN IN (S$): count% = 0: INPUT# i%, lnum%(count%) 130 WHILE lnum%(count%) > 0 140 count% = count% + 1: 150 IF count% >= 500 THEN PRINT "TOO MANY SOURCE LINES": STOP 160 INPUT# i%, lnum%(count%): WEND 170 CLOSE# i%: i% = OPENIN(S$) 180 o% = OPENOUT (o$) 190 count% = 0: INPUT LINE# i%, line$ 200 WHILE line$ > I 210 s% = skip num(1): PRINT# o%, count% * incr% + oset%; 220 p% = INSTR(line$, "GOTO") + 4: WHEN 2% IS = 4 THEN: 230 p% = INSTR(line$, "GOSUB") + 5: WHEN p% IS = 5 THEN: 240 p% = INSTR(line$, "RESTORE") + 7: IF p% = 7 THEN p% = 0 250 ENDWHEN 260 ENDWHEN 270 WHEN p% IS = 0 THEN: 280 PRINT# o%, MID$(line$, s%); 290 ELSE: 300 p% = skip_spc(p%): num% = VAL(MID$(line$, p%) ) 310 PRINT# o%, MID$(line$, s%, p%-s%);: p% = skip_num(p%) 320 WHEN num% IS > 0 THEN : 330 n% = 0: WHILE lnum%(n%) > 0 AND lnum%(n%) <> num% 340 n% = 6% + 1: WEND 350 WHEN lnum%(n%) IS = 0 THEN: 360 PRINT "ERROR in line "; lnum%(count%); ": can't find "; num% 370 PRINT# o%, "????"; 380 ELSE: 390 PRINT# o%, n% * incr% + oset%;: ENDWHEN 400 ENDWHEN 410 PRINT# o%, MID$(line$, p%); 420 ENDWHEN 430 INPUT LINE# i%, line$: count% = count% + 1 440 WEND 450 CLOSE# i%: CLOSE# o% 460 REM 470 DEF skip_spciptr%) 480 WHILE MID$(line$, ptr%, 1) = "1" 490 ptr% = ptr% + 1: WEND 500 = ptr% 510 DEF skip num(ptr%) 520 WHILE MID$(line$, ptr%, 1) >= "ON AND MID$(line$, ptr%, 1) <= "9" 530 ptr% = ptr% + 1: WEND 540 = ptr%
The following is a sample session where the prime number program from Appendix A is compiled to run as a CP/M command. User's input is in bold type.
B>b80 AB80 Basic compiler for Z80 >load "prime.bas" (load program) >comp "prime", &100,&3800, (compile to PRIME.HEX, using default for the top-of-RAM parameter) >sys (exit to system) B>a:sid rtd.hex (enter SID, read run time package) CP/M 3 SID - Version 3.0 NEXT MSZE PC END 3703 3703 0100 COFF #rprime.hex (read PRIME.HEX and convert to binary) NEXT MSZE PC END 397D 397D 0100 C9FF #wprime.com (write out PRIME.COM) 0071h record(s) written. #^C (user types control-C) B>prime (PRIME.COM can now be run as a normal CP/M command) 10 Iterations (program takes about 30s to run) 1006 Primes b:>
The following is a sample session where the prime number program from Appendix A is compiled on a CP/M system to run in ROM on the SCPUB. The user's input is in bold type.
B> b80 Basic compiler for Z80 >load "prime.bas" (load program) >comp "prime",&400,&4000,&f000 (compile to file PRIME.HEX) >sys (exit to system) B>a:sid (enter SID) CP/M 3 SID - Version 3.0 #rprime.hex (read PRIME.HEX and convert to binary) NEXT MSZE PC END (program start address is &4000 as 417D 417D 0100 C4FF specified in the comp command in AB80) (program end address is &417D) #wprime.epr, 4000,417D (write binary data to PRIME.EPR) #^C (user types Control-C) B>a:eprom (now blow program into EPROM) (before entering this command make sure you have an SEP board plugged into your STEbus backplane) #wprime.epr, 4000,417D *** EPROM program for CP/M - V1.1 *** 2716 - 1 2732 - 2 2764 - 3 27128 - 4 27256 - 5 : 3 (2764) Program EPROM . . . 1 Read EPROM . . . . 2 Check erased . . . 3 Reselect EPROM . . . 4 Exit to system . . . 5 : 1 (Program EPROM) Enter [dsk:]filename : prime.epr Data loaded : 0100 - 027FH (Where PRIME.EPR has loaded) Enter (hex) addresses Data start address : 100 (Use addresses above) Data end address : 27f EPROM start address : 0 (Blow into beginning of EPROM) Use fast algorithm (Y/N): y Enter RETURN or new value (Prog. volts x 10) = 210: RETURN (Enter RETURN for 21V) No errors
The compiled code runs at &4000 and is now blown into EPROM starting from EPROM address 0. This EPROM must be put into IC23 on the SCPUB. Put the runtime package ROM (AB80A) into IC22. Connect a terminal and switch on to run the program.