Contents / Reference section / Previous chapter / Next chapter / Index
Why use an assembly language rather than BASIC?
BASIC is a relatively easy language to learn and another of its great strengths is that when you make a mistake the computer tries to give you some helpful message explaining what you have done wrong - or what it is incapable of doing.
But there are prices to be paid for this "friendliness" and sometimes one has to communicate with the computer in its own language (machine code) or a low-level language like assembly language - despite all the problems this can bring. Assembly language consists of statements like
LDA #03
which means "Load the accumulator with 3" or
JSR &6000
which means "Jump to the subroutine at location 6000 hex"
An Assembler is a computer program which converts these assembly language "mnemonics" into the Machine code numbers that the computer actually understands.
Assembly language is used where high speed is vital or where the minimum amount of memory must be used. BASIC is used where ease of programming is more important than either speed or memory requirements. There are many other computer languages each with their own strengths and weaknesses.
It is unfortunately not possible, in a book this size, to teach you to write in assembly language, even though the BASIC interpreter contains an assembler. However those familiar with 6502 assembly language will need a good deal of information to enable them to write effective programs. The rest of this section explains, for those familar with 6502 assembly, the main features of the machine code access routines.
As the reader will be aware 16K of ROM is set aside for the Machine Operating System (MOS). The MOS looks after all input/output and is accessed via a set of well defined entry points, many of which are vectored via RAM.
Access is provided to all graphics and text features including MODE selection, line drawing, text and graphic windowing, redefinition of characters, 6845 CRTC registers, flash rate control.
Access is also provided to sound generation, clock set/read, parallel/serial printer I/O, baud rate selection, ADC results and event completion flags.
The machine code programmer may select a different file system (tape, disk or net) and may perform a wide range of file input/output to the selected file system.
Considerable efforts have been made to give the assembly language programmer access to entry points. Professional programmers will understand the need to use the entry points provided. These will remain static and will work in all configurations. The assembler is part of the BASIC interpreter and can be entered at any time by placing a left hand square bracket [ as a BASIC statement. The end of the assembly language section is indicated by a right hand square bracket ]. Note that in MODE 7, the Teletext mode, these symbols are displayed as left and right arrows ? and ? respectively.
10 PRINT "THIS IS BASIC"
20 [
30 JSR &FFE7
40 ]
(Do not try this program - it is incomplete and could prove 'fatal')!
In the example given the assembler has been given no indication of where to store the assembled machine code. The "program counter" is set by the variable P% and this can be set to any desired value e.g.
10 PRINT "THIS IS BASIC"
15 P%=&7000
20 [
30 JSR &FFE7
40 ]
50 PRINT "AND THIS IS BASIC TOO"
>RUN
THIS IS BASIC
7000
7000 20 E7 FF JSR &FFE7
AND THIS IS BASIC TOO
However another, and much more useful, way is provided to enable the programmer to allocate a few bytes of memory to a piece of machine code. A special version of the DIM statement is used.
10 PRINT "THIS IS BASIC"
15 DIM GAP% 20
16 P%=GAP%
20 [
30 JSR &FFE7
40 ]
50 PRINT "AND THIS IS BASIC TOO"
>RUN
THIS IS BASIC
0E74
0E74 20 E7 FF JSR &FFE7
AND THIS IS BASIC TOO
The above program dynamically allocates a gap of 21 bytes and the address of the start of the block is given in the variable GAP%. This is the safe way of inserting machine code sections into BASIC programs. Of course the actual location of the routine will change if more BASIC statements are inserted in the program before the DIM statement.
10 PRINT "THIS IS BASIC"
12 REM INSERT AN EXTRA LINE
15 DIM GAP% 20
16 P%=GAP%
20 [
30 JSR &FFE7
40 ]
50 PRINT "AND THIS IS BASIC TOO"
>RUN
THIS IS BASIC
0E8F
0E8F 20 E7 FF JSR &FFE7
AND THIS IS BASIC TOO
Two BASIC statements provide access to machine code routines: CALL and USR. The USR statement provides an easy way of passing values to and from the microprocessor's registers before and after the machine code call. When the statement
R = USR(Z)
is executed the computer first transfers values from the BASIC variables X%, Y%, A% and C% into the processor's X and Y register, accumulator and carry flag respectively. Of course, only the least significant byte of X%, Y% and A% can be used and only the least significant bit of C% can be copied into the carry flag.
After the various registers have been set up, control is passed to the machine code subroutine (at address Z). This routine must end with an RTS if control is to return to BASIC. On return the A, X and Y registers and the program status register (P) are used to produce a number which is placed in the BASIC variable R.
This can be neatly illustrated by a short program which uses an OSBYTE (Operating System Byte) call to determine the position of the text cursor on the screen.
30 A%=&86
40 X%=0
50 R=USR(&FFF4)
60 PRINT ~R
Lines 30 and 40 ensure that the accumulator and X register will be set up correctly. Line 50 obtains the result in R and line 60 prints the result in hexadecimal. A typical result would be
B1120086
B1 has come from the program status register (P)
12 from the Y register which gives the Y co-ordinate of the cursor
00 from the X register giving the X co-ordinate
86 from accumulator which was set up from A%
The CALL statement provides much more flexible access to machine code routines. CALL does not return a calculated result into a BASIC variable. However, the user can, if he or she wishes, make any BASIC variable or group of variables available to the machine code routine. If a list of BASIC variables follows the CALL statement then a "parameter block" will be set up starting at location &600. The sole purpose of this block is to let a machine code routine know where variables are stored in memory and what type each variable is.
The demonstration program which follows shows the use of the CALL statement and also of the error handling features.
10 REM Demonstration of CALL with a parameter
20 PROCINIT
30 A%=R12345678
40 CALL SWAP,A%
50 PRINT A%
60 END
70 DEF PROCINIT
80 DIM Q% 100
90 FOR C=0 TO 2 STEP 2
100 PAR=&600: REM Parameter block
110 ZP=&80: REM Usable zero page
120 P%=Q%
130 [OPT C
140 .SWAP LDA PAR
150 CMP#1:BEQ OK
160 .ERRO BRK
161 ]
162 ?P%=45
163 $(P%+1)="Swap Parameters"
164 P%=P%+16
170 [OPT C:BRK
180 .OK LDA PAR+3:CMP #4:BNE ERRO
190 LDA PAR+1:STA ZP
200 LDA PAR+2:STA ZP+1
210 LDY#3:LDX#0
220 LDA(ZP,X):PHA:LDA(ZP), Y:STA(ZP,X):PLA:STA(ZP),V
230 RTS
240 ] NEXT
250 ENDPROC
Lines 10 to 60 form the main BASIC program. This uses a machine code program to swap parts of an integer number around. Line 20 calls a procedure which assembles a piece of machine code. Line 30 sets A% to some arbitrary value and then line 40 calls the machine code subroutine to swap around. Line 50 shows A% after it has been "vandalised".
The procedure itself (lines 70 to 250) assembles the assembly language mnemonics into machine code. Line 80 tells the computer to allocate 101 bytes of memory and to set Q% to the address of the memory allocated. Lines 90 and 240 form a FOR...NEXT loop which makes the computer assemble the code twice - the first pass to work out the addresses of the labels, and the second pass to put them into code. Line 120 sets the program counter at the start of each of the two passes. Lines 140 and 150 check to see that the value in location &600 is 1. Location &600 contains the "number of parameters" and our machine code routine is only meant to have one parameter. See page 214 for information about parameter passing from CALL. If location &600 does not contain 1 then control will pass to the routine labelled ERRO (line 160).
This routine shows how a user supplied routine can produce standard BASIC error messages and error numbers. This is explained further on page 464. Suffice it to say that here the code will generate BASIC error number 45 (a new one) and will issue the message Swap Parameters before returning control to BASIC. Line 164 increments the internal program counter enough to allow space for the messages etc.
Line 170 re-enters normal assembler operation and re-sets the OPT. Line 180 checks to see that the parameter is of type 4 (an integer variable - see page 211) and if not reports an error. Lines 190 to 220 perform the actual swap and line 230 ensures an orderly return to BASIC.
The assembler is entered with a statement starting with [ and exited with ]. In Teletext mode these characters are displayed as 'arrow left' and 'arrow right'. In the assembly language section the pseudo-operation OPT can be used to control listing and error reporting. See the keyword OPT for more details, in brief:
OPT 0 gives no errors and no listing
OPT 1 gives no errors and a listing
OPT 2 reports errors but gives no listing
OPT 3 reports errors and gives a listing.
Normally the "first pass" should be with OPT 0 and the "second pass" with OPT 2. If a listing is required then the "second pass" should be OPT 3.
Labels are defined by preceding them with a full stop. No full stop is required when they are referenced.
LDA #8 .LOOP BIT VIA+&D BNE LOOP
Labels are normal BASIC variable names. Thus they should start with a letter and not start with a reserved word. During the "first pass" unknown labels will generate an address of the current op-code. Thus JMP and branch instructions will jump or branch to themselves.
To enter byte or string constants the programmers should leave the assembler temporarily and use indirection operators. For example
JMP LABEL1
]
$P%="TODAY"
P%=P%+6
.LABEL1 LDA&
Remembering that the BASIC variable P% is used to store the value of the program counter, the string indirection operator ($) can be used to insert ASCII characters directly into memory. The word TODAY will be copied to memory followed by a carriage-return (&0D) -, a total of 6 characters. If you wish to follow the word by a byte containing zero instead of &0D you could write
$P%="TODAY"
P%?5=0
P%=P%+6
To insert numeric values directly into memory you can use byte or word indirection operators:
JMP LABEL2
]
?P%=10
P%?1=20
P%?2=66
P%=P%+3
[
The various 6502 addressing modes are indicated in assembly language in the standard way, but using an ampersand to represent a hexadecimal number.
Addressing mode | Examples | |
Immediate | LDA #4 | LDA #VALUE |
Absolute | JMP &8000 | LDA LOCATION |
Zero page | LDA &FC | STY TEMPI |
Accumulator | ASL A | |
Implied | RTS | |
Pre-indexed indirect | LDA (5,X) | CMP(OFFSET, X) |
Post-indexed indirect | LDA (&84),Y | ORA (TABLE),Y |
Zero page X | LDA 10,X | INC CNT,X |
Absolute X | BNE &3210 | BNE LOOP3 |
Relative Indirect | JMP (&20E) | JMP (WRCHV) |
Zero page indexed with Y | LDX 24,Y | LDX VAR,Y |
Comments can be inserted into assembly language by preceding them with a backslash. On the keyboard and in modes 0 to 6 this is shown as \ whereas in Teletext mode it is displayed as one-half (½). In BASIC everything after a REM statement is ignored on the current line so it is not possible to put another statement on the same line. Thus
10 REM HELLO : PRINT "FRIDAY"
would not print the word FRIDAY. However, in assembly language a comment terminates at the end of the statement. All the following statements would be assembled
LDA #&10 \MASK :.LOOP BIT VIA+13
\INTERRUPT FLAG :BEQ LOOP
The BBC computer is unusual in a number of respects, not least because of the care taken to ensure that everything that can be done by programs written in the "Input/Output processor" (the BBC computer) can also be done in the "second processor" which is on the far side of the Tube®.
If a piece of machine code alters a particular memory location that controls the screen display directly, then that same piece of machine code will not work in the second processor because the screen will not be affected by the memory location in the second processor.
It is vital that programmers avoid reading and writing to specific memory locations such as the screen memory, zero page locations used by BASIC and memory mapped input/output devices. System calls are provided to enable you to access all these important locations and use of these system calls will ensure that your programs interact successfully with the machine. Don't feel that we are trying to hide anything from you, on the contrary we are offering you access to all the I/O routines that BASIC uses! Cultivate the habit of using system calls and then you will not need to re-write your code when you move it to the second processor.
Machine code user programs should communicate with the operating system by calling routines in the address range &FF00 to &FFFF. These routines then call a specific internal routine whose address may change in different operating systems. The address of the specific routine is held in RAM between locations &200 and &2FF. The user may change the address held in these RAM locations to intercept any operating system call he wishes.
Thus the "output the character in A" routine is entered at &FFEE in all environments. The routine indirects through location &20E in all machines. The contents of locations &20E and &20F will vary depending on the machine and the version of the operating system. In one particular machine the address in &20E and &20F is &E1DC which is the local internal address of the normal "output character in A" routine.
Parameters are passed to the routines in various ways using either the 6502 A, X and Y registers, zero page locations or a parameter block. All routines should be called with a JSR and with the decimal flag clear (i.e. in binary mode).
In the detailed descriptions which follow A refers to the accumulator, X and Y refer to the registers,
C, D, N, V, and Z refer to the processor flags.
Below is a summary of operating system calls and indirection vectors.
Routine | Vector | Summary of function | ||
Name | Address | Name | Address | |
UPTV | 222 | User print routine | ||
EVNTV | 220 | Event interrupt | ||
FSCV | 21E | File system control entry | ||
OSFIND | FFCE | FINDV | 21C | Open or close a file |
OSGBPB | FFD1 | GBPBV | 21A | Load or save a block of memory to a file |
OSBPUT | FFD4 | BPUTV | 218 | Save a single byte to file from A |
OSBGET | FFD7 | BGETV | 216 | Load a single byte to A from file |
OSARGS | FFDA | ARGSV | 214 | Load or save data about a file |
OSFILE | FFDD | FILEV | 212 | Load or save a complete file |
OSRDCH | FFE0 | RDCHV | 210 | Read character (from keyboard) to A |
OSASCI | FFE3 | ?? | ?? | Write a character (to screen) from A plus LF if (A)=&0D |
OSNEWL | FFE7 | ?? | ?? | Write LF,CR (&0A,&0D) to screen |
OSWRCH | FFEE | WRCHV | 20E | Write character (to screen) from A |
OSWORD | FFF1 | WORDV | 20C | Perfrom miscellaneous OS operation using control block to pass parameters |
OSBYTE | FFF4 | BYTEV | 20A | Perfrom miscellaneous OS operation using registers to pass parameters |
OSCLI | FFF7 | CLIV | 208 | Interpret the command line given |
IRQ2V | 206 | Unrecognised IRQ vector | ||
IRQ1V | 204 | All IRQ vector | ||
BRKV | 202 | Break vector | ||
USERV | 200 | Reserved |
Files are treated as a sequence of 8 bit bytes. They can be accessed in one operation (using OSFILE) or in blocks (using OSGBPB) or a byte at a time (using OSBGET and OSBPUT). The following attributes may be associated with each file.
Load address is the address in memory to which the file should normally be loaded. This can be over-ridden when the file is loaded, if necessary.
Execution address is meaningful only if the file contains executable machine code, in which case it is the address where execution should start. If the file contains a high level language program then the execution address in unimportant.
Length is the total number of bytes in the file. It may be zero.
Pointer is an index pointing to the next byte of data that is to be processed. The value of "Pointer" may be read or written (using OSARGS), and it does not indicate whether the appropriate byte has yet been transferred from file to memory or vice versa. Pointer is automatically updated by 0SBGET and OSBPUT.
Opens a file for writing or reading and writing. The routine is entered at &FFCE and indirects via &21C. The value in A determines the type of operation.
A=0 | causes a file or files to be closed. |
A=&40 | causes a file to be opened for input (reading). |
A=&80 | causes a file to be opened for output (writing). |
A=&C0 | causes a file to be opened for input and output (random access). |
If A=&40, &80 or &C0 then Y(high byte) and X(low byte) must contain the address of a location in memory which contains the file name terminated with CR (&0D). On exit Y will contain the channel number allocated to the file for all future operations. If Y=0 then the operating system was unable to open the file.
If A=0 then a file, or all files, will be closed depending on the value of Y. Y=0 will close all files, otherwise the file whose channel number is given in Y will be closed.
On exit C, N, V and Z are undefined and D=0. The interrupt state is preserved, however interrupts may be enabled during the operation.
The operating system call to get or put a block of bytes to a file which has been opened with OSFIND. The routine is entered at &FFD1 and vectors via &21A. It is not available on cassette systems, and is documented with the disc system.
Writes (puts) a byte in A to the file previously opened using OSFIND. The routine is entered at &FFD4 which indirects through &218. On entry Y contains the channel number allocated by OSFIND.
On exit A, X and Y are preserved, N, V and Z are undefined and D=0. The interrupt state is preserved but interrupts may be enabled during the operation.
Gets (reads) a byte from a file into A. The file must have been previously opened using OSFIND and the channel number allocated must be in Y. The routine is entered at &FFD7 which indirects via &216.
On exit C=0 indicates a valid character in A. C=1 indicates an error and A indicates the type of error, A=&FE indicating an end-of-file condition. X and Y are preserved, N, V and Z are undefined and D=0. The interrupt state is preserved but interrupts may be enabled during the operation.
This routine enables a file's attributes to be read from file or written to file. The routine is entered at &FFDA and indirects via &214. On entry X must point to four locations in zero page and Y contains the channel number.
If Y is non-zero then A will determine the function to be carried out on the file whose channel number is in Y.
A= 0 | read sequential pointer |
A= 1 | write sequential pointer |
A= 2 | read length |
A=&FF | "ensure" that this file is up to date on the media |
If Y is zero then the contents of A will determine the function to be carried out.
A= 0 | will return, in A, the type of file system in use. The value of A on exit has the following significance
0- no file system 1- 1200 baud cassette file system 2- 300 baud cassette file system 3 ROM pack file system 4- Disc file system 5- Econet file system 6- Teletext/Prestel Telesoftware file system | /tr> |
A= 1 | return address of the rest of the command line in the zero page locations | |
A=&FF | "ensure" that all open files are up to date on the media. |
On exit X and Y are preserved, C, N, V and Z are undefined and D=0. The interrupt state is preserved but interrupts may be enabled during the operation.
This routine, by itself, allows a whole file to be loaded or saved. The routine is entered at &FFDD and indirects via &212.
On entry A indicates the function to be performed. X and Y point to an 18 byte control block anywhere in memory. X contains the low byte of the control block address and Y the high byte. The control block is structured as follows from the base address given by X and Y.
00 01 | Address of file name, which must be terminated by &0D | LSB MSB |
02 03 04 05 | Load address of file | LSB MSB |
06 07 08 09 | Execution address of file. | LSB MSB |
0A 0B 0C 0D | Start address of data for write operations, or length of file for read operations | LSB MSB |
0E 0F 10 11 | End address of data, that is byte after last byte to be written or file attributes. | LSB MSB |
The table below indicates the function performed by OSFILE for each value of A.
A=0 | Save a section of memory as a named file. The file's catalogue information is also written |
A=1 | Write the catalogue information for the named file |
A=2 | Write the load address (only) for the named file |
A=3 | Write the execution address (only) for the named file |
A=4 | Write the attributes (only) for the named file |
A=5 | Read the named file's catalogue information. Place the file type in A |
A=6 | Delete the named file |
A=&FF | Load the named file and read the named file's catalogue information |
When loading a file the byte at XY+6 (the LSB of the execution address), determines where the file will be loaded in memory. If it is zero then the file will be loaded to the address given in the control block. If non-zero then the file will be loaded to the address stored with the file when it was created.
The file attributes are stored in four bytes. The least significant 8 bits have the following meanings
Bit | Meaning |
0 | not readable by you |
1 | not writable by you |
2 | not executable by you |
3 | not deletable by you |
4 | not readable by others |
5 | not writable by others |
6 | not executable by others |
7 | not deletable by others |
File types are as follows
0 | nothing found |
1 | file found |
2 | directory found |
A BRK will occur in the event of an error and this can be trapped if required. See page 464 which deals with BRK handling.
On exit X and Y are preserved, C, N, V and Z are undefined and D=0. The interrupt state is preserved but interrupts may be enabled during the operation.
This routine reads a character from the currently selected input stream into A. The routine is called at location &FFE0 and indirects via &210. The input stream can be selected by an OSBYTE call with A=2. See page 421.
On exit C=0 indicates a successful read and the character will be in A. C=1 indicates an error and the error type is returned in A. If C=1 and A=&1B then an escape condition has been detected and the user must at least acknowledge this by performing an OSBYTE call with A=&7E; BASIC will normally do this for you. X and Y are preserved, N, V and Z are undefined and D=0. The interrupt state is preserved.
This routine writes the character in A to the currently selected output stream by using OSWRCH. However, if A contains &0D then OSNEWL is called instead. The actual code at location &FFE3 is
FFE3 | C9 | 0D | OSASCI | CMP #&0D |
FFE5 | D0 | 07 | BNE OSWRCH | |
FFE7 | A9 | 0A | OSNEWL | LDA #&0A |
FFE9 | 20 | EEFF | JSR OSWRCH | |
FFEC | A9 | 0D | LDA #&0D | |
FFEE | 6C | 0E02 | OSWRCH | JMP(WRCHV) |
On exit A, X and Y are preserved, C, N, V and Z are undefined and D=0. The interrupt state is preserved.
This call issues a LF CR (line feed, carriage return), to the currently selected output stream. The routine is entered at &FFE7.
On exit X and Y are preserved, C, N, V and Z are undefined and D=0. The interrupt state is preserved.
This call writes the character in A to the currently selected output stream. The output stream may be changed using an OSBYTE call with A=3. See page 422 for more details. OSWRCH is entered at location &FFEE and indirects via &20E.
On exit A, X and Y are preserved, C, N, V and Z are undefined and D=0. The interrupt state is preserved but interrupts may be enabled during the operation.
All character output from BASIC, the operating system and anything else uses this routine. It is, therefore, easy to pass all output to a user provided output routine by placing the address of the user routine at WRCHV (&20E). However, the user should note that all control characters have special significance. For example, &1C is followed by 4 bytes which defines a text window. See the section on VDU codes for a complete listing of control characters. If the user wishes to intercept any control characters then his or her routine must check for all control characters. The routine must arrange to
skip however many bytes follow a particular code since these bytes might, inadvertently, contain a control code. For example, the BASIC statement
GCOL 1,3
is passed to the operating system as a string of bytes through OSWRCH. In fact, in this case the bytes would be &12,1,3.
The OSWORD routine invokes a number of miscellaneous operations all of which require more parameters or produce more results than can be passed in A, X and Y. As a result, all OSWORD calls use a parameter block somewhere in memory. The exact location of the parameter block is given in X (low byte) and Y (high byte). The contents of A determine the exact nature of the OSWORD call.
All OSWORD calls are entered at location &FFF1 which indirects through &20C. The table below summarises the OSWORD calls available in release 1.0 of the operating system.
A= | Summary of function |
0 | Read a line rom the current input stream to memory |
1 | Read the elapsed-time clock |
2 | Write the elapsed-time clock |
3 | Read internal timer |
4 | Write internal timer |
5 | Read a byte in the input/output processor memory |
6 | Write a byte in the input/output processor memory |
7 | Generate a sound |
8 | Define an envelope for use with the sound statement |
9 | Read pixel colour at screen position X, Y |
A | Read dot pattern of a specific displayable character |
B | Read the palette value for a given logical colour |
This routine accepts characters from the current input stream and places them at a specified location in memory. During input the DELETE code (ASCII 127) deletes the last character entered, and CTRL U (ASCII 21) deletes the entire line. The routine ends if RETURN is entered (ASCII 13) or an ESCAPE condition occurs.
The control block contains 5 bytes
00 01 | Address of buffer for input line | LSB MSB |
02 | Maximum length of line | |
03 | Minimum acceptable ASCII value | |
04 | Maximum acceptable ASCII value |
Characters will only be entered if they are in the range specified by XY+3 and XY+4.
On exit C=0 indicates that (CR; ASCII code 13 or &D), ended the line. C not equal to zero indicates that an escape condition terminated entry. Y is set to the length of the line, including the CR if C=0.
OSWORD call with A=1 Read clock
This call is used to read the internal elapsed-time clock into the 5 bytes pointed to by X and Y. This clock is the one used by BASIC for its TIME function. The elapsed-time clock is reset to zero when the computer is switched on and if a hard-reset is executed. Otherwise it is incremented every hundredth of a second. The only thing that will cause it to lose time is pressing the BREAK key and keeping it pressed.
On entry X and Y should point to the memory locations where the result is to be stored. Y contains the high byte and X the low byte of the address.
On exit X and Y are undefined and the time is given in location XY (lsb) to XY+4 (msb). The time is stored in pure binary.
OSWORD call with A=2 Write clock
This call is used to set the internal elapsed-time clock from the 5 bytes pointed to by XY.
On entry X and Y should point to the memory locations where the new time is stored. Y contains the high byte and X the low byte of the address. The least significant byte of the time is stored at the address pointed to by XY and the most significant byte of the time is stored at address XY +4. A total of 5 bytes are required.
OSWORD call with A=3 Read interval timer
In addition to the clock there is an interval timer which is incremented every hundredth of a second. The interval is stored in 5 bytes pointed to by X and Y. See OSWORD with A=1.
OSWORD call with A=4 Write interval timer
On entry X and Y point to 5 locations which contain the new value to which the clock is to be set. The interval timer increments and may cause an event (see page 464) when it reaches zero. Thus setting the timer to &FFFFFFFFFE would cause an event after 2 hundredths of a second.
OSWORD call with A=5 Read I/O processor memory
This call enables any program to read a byte in the I/O processor no matter in which processor the program is executing.
On entry X and Y point to a block of memory as follows
XY | LSB of address to be read |
XY+1 | |
XY+2 | |
XY+3 | MSB of address to be read |
On exit the 8 bit byte will be stored in XY+4.
OSWORD call with A=6 Write to I/O processor memory.
As pointed out elsewhere, programs that are to work through the Tube® must not attempt to access memory locations in the I/O processor directly. This call provides easy access to
locations in the BBC Microcomputer wherever the user's program happens to be.
On entry X and Y point to a block of memory initialised as follows:
XY | LSB of address to be changed |
XY+1 | |
XY+2 | |
XY+3 | MSB of address to be changed |
XY+4 | byte to be entered at address given |
OSWORD call with A=7 Make a sound
This call can be used to generate a sound. The 8 bytes pointed to by locations XY to XY+7 are treated as four 2-byte values. These four values determine the sound effect. See the keyword SOUND for a detailed description.
XY | channel | LSB | 1 | 01 |
XY+1 | MSB | 00 | ||
XY+2 | amplitude | LSB | -15 | F1 |
XY+3 | MSB | FF | ||
XY+4 | pitch | LSB | 200 | C8 |
XY+5 | MSB | 00 | ||
XY+6 | duration | LSB | 20 | 14 |
XY+7 | MSB | 00 |
The example figures on the right of the table show first the required decimal value and secondly the 2 hexadecimal values required. The figures are only illustrative.
On exit X and Y are undefined.
OSWORD call with A=8 Define an envelope
This call is used to define an envelope which can be used by a SOUND statement or equivalent OSWORD call. On entry X and Y point to an address in memory where 14 bytes of data are stored. Y contains the high part of the address and X the low part. The envelope number is stored at XY and the following 13 locations contain data for that envelope. See the entry for the ENVELOPE keyword for more details.
On exit X and Y are undefined.
OSWORD call with A=9 Read a pixel
This call enables the machine code programmer to read the status of a graphics point at any specified location. On entry X and Y point to a block of 5 bytes. Y contains the most significant byte of the address and X the least significant byte. On entry the first four bytes are set up thus:
XY | LSB of X co-ordinate |
XY+1 | MSB of X co-ordinate |
XY+2 | LSB of Y co-ordinate |
XY+3 | MSB of Y co-ordinate |
On exit XY+4 contains the logical colour of the point or &FF if the point is off the screen. X and Y are undefined.
OSWORD call with A=&0A Read character definition.
Characters are displayed on the screen as an 8 by 8 matrix of dots. The pattern of dots for each character in MODES 0 to 6, including user-defined characters, is stored as 8 bytes (see page 384). This call enables the 8 bytes to be read into a block of memory starting at an address given in X and Y.
On entry the ASCII code of the character is the first entry on the block.
On exit the block contains data as shown below. X and Y are undefined.
XY | Character required |
XY+1 | Top row of displayed character |
XY+2 | Second row |
... | |
XY+8 | Bottom row of displayed character |
OSWORD call with A=&0B Read palette
The reader will be aware that each logical colour (0 to 15) has a physical (or displayed) colour associated with it. The physical to logical association can be changed with VDU 19. This OSWORD call enables one to determine the physical colour currently assigned to each logical colour. On entry the X and Y registers contain the address of the start of a block of 5 bytes.
The first byte should contain a value representing the logical colour.
On exit the following four bytes will contain the same four numbers used when VDU 19 assigned a physical colour to the same logical colour. Suppose that logical colour 2 was in fact set to blue (4) by the statement
VDU 19,2,4,0,0,0
then this call would produce the following result:
XY | 2 | Logical colour |
XY+1 | 4 | Physical colour (blue) |
XY+2 | 0 | For future expansion |
XY+3 | 0 | ... |
XY+4 | 0 | ... |
Command Line Interpreter (&FFF7)
The machine operating system CLI is usually accessed from a high level language by starting a statement with an asterisk. For example:
*MOTOR 0,1
The command line itself (excluding the asterisk) is then passed, without any further processing, to the CLI.
Machine code programs can use all operating system commands by placing the address of a command line in the X(LSB) and Y(MSB) registers and calling &FFF7. This routine indirects through location &208.
The command line should not start with an asterisk and must end with an &0D. In fact any leading asterisk or spaces will be stripped.
The following BASIC program illustrates this:
10 DIM C 20
20 $C="MOTOR 1"
30 X%=C MOD 256
40 Y%=C DIV 256
50 CALL &FFF7
When RUN the cassette motor will turn on. The computer will have allocated a space for C - perhaps at location &E0A in which case successive bytes would contain:
Address Contents &E0A 4D (M) &E08 4F (O) &E0C 54 (T) &E0D 4F (O) &E0E 52 (R) &E0F 20 (space) &E10 31 (1) &E11 00 (return)
Of course, this particular example would have been easier as an
FX call or simply as *MOTOR 1. However, complex commands may need this call.
It is necessary to provide some means to enable programs to deal with faults such as Illegal command or Division by zero. BASIC uses the 6502 BRK instruction when dealing with faults like this and user written programs can also use the same facility. In BASIC (for example), a BRK instruction is followed by a sequence of bytes giving the following information:
BRK instruction - value &00
Fault number
Fault message - may contain any non-zero character
&00 to terminate message
When the 6502 encounters a BRK instruction the operating system places the address following the BRK instruction in locations &FD and &FE. Thus these locations point to the "Fault number". The operating system then indirects via location &202. In other words control is transferred to a routine whose address is given in locations &202 (low byte) and &203 (high byte). The default routine, whose address is given at the location, prints the fault message.
The BRK handling outline above enables the user to intercept normal procedures and to generate his or her own special messages and error numbers in user written machine code routines. The program on page 446 shows this in practice. See also page 466 (IRQ).
Whilst faults are, in general, "fatal", there is another class of events, called "Events", which are informative rather than fatal. This class of events includes, for example, a key being pressed on the keyboard. The user may wish to detect such an operation or may be happy to ignore it. When the operating system detects an "Event" then, if that event is enabled (by using *FX 14) it indirects via &220 with an event code in the accumulator. The contents of X and Y depend on the event. The event codes in A indicate the following:
Accumulator description |
0 | Buffer empty. | X=buffer identity |
1 | Buffer full. | X=buffer identity Y=character that could not be stored |
2 | Keyboard interrupt | |
3 | ADC conversion complete | |
4 | Start of TV field pulse (vertical sync) | |
5 | Interval timer crossing zero | |
6 | Escape condition detected | |
The user supplied event handling routine is entered with interrupts disabled and it should not enable interrupts. The routine should return (RTS) after a short period, say one millisecond maximum, and should preserve the processors P, A, X and Y registers.
The whole machine runs under continuous interrupts but nonetheless the user can easily add his or her own interrupts and handling routines. Because the machine runs under interrupts, software timing loops should not be used. Several hardware timers are available to the user and these should be used wherever possible.
NMI Non-Maskable Interrupt
In general these should be avoided. When a disc operating system ROM is fitted NMI's will be handled by the ROM. Again, it should be emphasised that NMI is reserved for the Operating System.
IRQ Interrupt Request
When an IRQ is detected the operating system immediately indirects through location &204 (IRQ1V) to an operating system routine which handles all anticipated internal IRQ's. If the operating system is unable to deal with the IRQ (because it has come from an unexpected device such as the user 6522), then the system indirects through &206 (IRQ2V). Thus the user routine for handling IRQ's should normally be indirected via IRQ2V but if top priority is required the user routine can be indirected via IRQ1V.
In either case the user supplied routine must return control to the operating system routine to ensure clean handling.
The operating system handles BRK and IRQ's with the following code
STA | &FC | \ temporary for A | |
PLA | |||
PHA | \ get processor status | ||
AND | #&10 | ||
BNE | BRK | ||
JMP | (&0204) | \ IRQ1V | |
BRK | TXA | \ BRK handling | |
PHA | \save X | ||
TSX | |||
LDA | &103,X | \get address low | |
CLD | |||
SEC | |||
SBC | #1 | ||
STA | &FD | ||
LDA | &104,X | \ get address high | |
SBC | #0 | ||
STA | &FE |
Note that A is stored in location &FC so that it can be accessed by user routines. When the computer indirects through &202 (BRKV), &204(IRQ1V) and &206(IRQ2V) X and Y will contain correct values. The user must not enable interrupts during his or her IRQ service routine.
This facility is only available from release 1.0.