The majority of BBC OS routines are said to be "vectored" (See the last article). The OS, on being told to call the OSCLI routine, first calls a routine at address &FFF7. This routine then calls the main OSCLI routine but not directly. It finds out the address at which the OSCLI routine is to be found by inspecting the contents of two bytes in page 2 of the RAM. These two bytes are called a *vector*: the low byte of the address of the routine concerned is found in the lower number byte of the vector and the high byte of the address is to be found in the higher numbered byte of the vector. Thus, for OSCLI, which is vectored through locations &208 and &209, the low byte of the OSCLI address is held in location &208 and the high byte is held in location &209. This is known as the Lo-Hi addressing convention and is followed for all stored addresses in all 6502 machines. The addresses held in each vector are set up by the OS whenever the machine is reset. Why bother with such a complicated way of calling a routine in the OS? It's not because Acorn are determined to make the life of the programmer as miserable as possible. On the contrary, this process is designed to make life easy! How can this be?
You may have noticed that all BBC OS routines mentioned so far are called at an address in the range &FF00 to &FFFF. This is no accident. When such an address is called, a routine is entered that causes a jump to the address held in the vector for that particular OS routine, as we've seen in the case of the CLI and the OSCLI call. Now, the address that we call between &FF00 to &FFFF is the same in *all* BBC OS versions and will continue to be in all versions to come. If it becomes necessary to change the OS ROM internal programs then the OS designers simply ensure that the addresses of the ROM routnes that are put in the vector locations are altered to take the changes into account. The user is thus protected from such changes in the OS provided that the OS routines are called at the correct entry points. The contents of a vector may therefore differ in different versions of the OS, but you won't notice this as long as you use the entry point addresses in range &FF00 to &FFFF.
A second advantage of the use of vectors is that this method provides us with a means of modifying the behaviour of the OS routines. We can simply alter the contents of a vector so that it points to a machine code routine of our own devising if we so desire, thus intercepting the normal OS calls. In later parts of the course, we'll look at the vectors that are used with each of the major OS routines.
For the moment, let's consider the vector called USERV, which is pointed to at locations &200 and &201. This is rather a special vector, in that it normally does nothing. It is used by two * commands, called *CODE and *LINE. If you type these in normally then the message 'Bad Command' is issued. Before sending off a letter of complaint about a new BBC OS bug, read on!
USERV enables us to define the function carried out by the *CODE and *LINE commands - user defined commands, if you like! Why should we want to do this? Well, *CODE is a particularly useful way of passing parameters into machine code programs, as shown in the following table:
Register *CODE x,y *LINE Text String -------- --------- ----------------- A 0 1 X Holds the value of the first Holds the low byte of the parameter after *CODE. address in memory at which you can find the first Parameter must therefore be character of the string between 0 and 255 after *LINE Y Holds the value of the Holds the high byte of the second parameter after the address in memory at which *CODE command. Again the you can find the first parameter must be between 0 character of the string and 255 after *LINE
This table shows the state of the three CPU registers on entering the routine pointed to by the contents of USERV. The A register holds either zero or one, and thus indicates which of the two commands caused USERV to be entered. X and Y hold values depending on whether it was a *CODE or a *LINE command. Thus, *CODE 3,2 will enter the routine pointed to by USERV with 0 in A, 3 in X, and 2 in Y. Obviously, the routine pointed to by the address held in USERV will be the routine that we want to pass the parameter to.
The program given below shows a simple *CODE command in action. The machine code routine itself is assembled into memory starting at address &C00 as a result of the assignment statement in line 40 - the integer variable P% 'maps onto' the processor program counter, just as A%, X% and Y% map onto the A, X and Y processor registers. USERV is set up to point to the routine by putting the low byte of this address, &00, in location &200, and the high byte, &0C, in location &201. The routine prints to the screen a given number of characters; the first parameter of the *CODE command holds the ASCII code of the character and the second parameter is the number of times that the character is to be printed to the screen.
*LINE isn't as generally useful as *CODE, but if you want to use it then the principles shown in the following program can be applied, as long as you remember that you will enter your program with one in the A register and the X and Y registers pointing to the text string in memory. This is the main function of *LINE, passing text strings over to machine code programs. For situations where there aren't many parameters to pass to your routine, these two calls are the most elegant way of doing it.
10 DIM C 100 20 userv=&200 30 ?userv=&00 40 userv?1=&0C 50 FOR I%=0 TO 2 STEP 2 60 P%=&C00 70 [ OPT I% 80 CMP #0 90 BNE notcode 100 TXA 110 .loop 120 JSR &FFE3 130 DEY 140 BNE loop 150 .notcode 160 RTS 170 ]:NEXT 190 FOR rep=1 TO 10 200 FOR asc=33 TO 48 210 code$="*CODE "+STR$asc+","+STR$rep 220 $C=code$ 230 X%=C MOD 256 240 Y%=C DIV 256 250 CALL &FFF7 260 NEXT:NEXT
Line 30 of the program sets up the USERV to point to our machine code routine. The loop between line 110 and 140 prints the character whose ASCII code is in the A register to the screen Y times. If the routine is entered by a *LINE command, lines 80 and 90 detect this and quit the routine. Lines 190 to 230 actually issue the *CODE command with variable parameters.
User Interaction
The main methods of interaction with a microcomputer are via the keyboard and the VDU or television screen. Our detailed investigation of the BBC Micro's Operating System continues with a discussion of the ways in which the machine's OS enables us to interact with these two vital areas of the computer.
Let's begin by examining the OS call that enables us to read characters from the currently selected input stream. This routine, named OSDRCH is called at address &FFE0 and is vectored through locations &210 and &211. As it accepts single characters from the currently selected input stream, we should first look at how we select the input stream. There are two major input streams - the keyboard and the RS423 input. We can select one of these by means of an OSBYTE, or *FX, call. The following table shows this command in both machine code and BASIC.
Selecting the Input Stream: n Keyboard RS423 Assembler BASIC - -------- ----- --------- ----- 0 o x LDA #2 *FX2,n 1 x o LDX #n 2 o o JSR &FFF4
Thus *FX2,1 disables the keyboard and enables the RS423 as the current input stream. Data received on the RS423 input would be treated as if it were being typed in to the computer. In assembler, to do the same job, 'n' would have a value of one.
Once you've set up the input stream that you wish to use, you can access it with OSRDCH. The first thing to say about OSRDCH is that it's really only useful in assembler programs - BASIC is obviously well endowed with input routines such as GET and INPUT. We call this routine at address &FFE0, and after return from the call, the character read in from the input stream is in the A register; if an error has been detected during the read operation it is reset to zero. So if C=1 on return from the OSRDCH routine, the character code contained in the A register is probably invalid in some way. When we're reading from the keyboard, this error is often caused ESCAPE being pressed. This situation is indicated by C=1 and the A register holding the value 27 (ASCII value for ESCAPE). If you detect this situation, then it is *vital* to act upon it; the BBC OS expects such an ESCAPE error to be acknowledged by the program.
We do this by using an OSBYTE call with A=126. This cleans up various parts of the BBC OS workspace in response to the ESCAPE error. The acknowledgement operation is, of course, usually done automatically by the BASIC interpreter during an input operation if ESCAPE is pressed. The simple routine that follows reads the current input stream and acts accordingly if an ESCAPE error is detected:
1000 .input JSR &FFE0 1010 BCS error 1020 RTS 1030 .error CMP #27 1040 BNE out 1050 LDA #126 1060 JSR &FFF4 1070 .out RTS
Line 1000 calls the OSRDCH routine, and 1010 checks the carry flag. If it is clear, then an RTS to the calling program is executed, with a legal character in the A register. Otherwise line 1030 checks to see if the error was caused by an ESCAPE event, and, if it was, lines 1050 and 1060 execute the OSBYTE call that acknowledges this. You might think that in order to enter strings of data into your machine code programs you would have to use a routine of your own devising, but you don't. There exists in the OS a means of reading strings of characters from the currently selected input stream. This routine is accessed via one of the OSWORD calls, which will be covered in more detail later in the course. However, we'll use this particular OSWORD call now to read in strings of characters.
The OSWORD routines are called at address &FFF1. There are several of these and we specify which we require by the value held in the A register when the call is made. In all OSWORD calls, the X and Y registers of the 6502 point to a block of memory called a 'control block', which holds the parameters that are to be passed over to the routine. The X register holds the low byte of the control block address and the Y register holds the high byte of the address - this follows 6502 Lo-Hi addressing convention. the way in which the control block is set up is shown here:
The OSWORD Control Block: Control Block Entry Function ------------------- -------- 0 Low byte of address to which the characters are to be written 1 High byte of the address to which the characters are to be written 2 Maximum line length 3 Minimum acceptable ASCII code 4 Maximum acceptable ASCII code
During the inputing of characters by this routine, the Delete key has its usual function. The routine can be exited by pressing RETURN or ESCAPE. For example, the control block that follows has these results when the OSWORD call is made:
Example OSWORD Control Block: Control Block Entry Value ------------------- ----- 0 &00 1 &0C 2 &07 3 32 4 96
- The first character input will be stored at &C00, the second at &C01, and so on.
- Only seven characters will be accepted; if you try to type in more characters than this then a 'beep' will be generated and the additional characters will be ignored.
- Only characters with ASCII codes between 32 (the Space character) and 96 (the £ character) will be accepted; others will be ignored.
We've now seen how easy it is to read data into the BBC Micro. Let's proceed to look at means of sending characters to the currently selected output stream. Again, we use an OS call to select the output stream to be used. This is *FX3,n - where 'n' specifies the stream to be selected. Each bit of the 'n' parameter controls a different output stream. As an example, *FX3,1 enables the serial, screen and printer output and allows SPOOLed output, provided that a *SPOOL command has been issued.
Output Stream Control Parameter Table: Bit 0 1 2 3 4 6 --- - - - - - - Value 1 RS423 Screen Printer Printer Spooled Printer on off off on off off** Value 0 off on on off* on on**
*Printer is on or off independent of whether printer is disabled by other means. **Printer off unless character is preceded by a CHR$1.
The main routine that is used for sending characters to the current output stream is named OSWRCH and is called at address &FFEE, vectored through locations &20E and &20F. It is very easy to use; simply load the A register with the ASCII code of the character that you want to write and then call the routine. All three following routines print the character 'A' to the screen:
1000 VDU 65 1000 PRINT CHR$65 1000 LDA #65 1010 JSR &FFEE
The BASIC VDU command has virtually the same effects as using OSWRCH. Characters in the ASCII range 32 to 255 print characters on the screen, with the exception of ASCII code 127, which is the Delete character. The characters in the range from 0 to 31, however, have special functions, which we'll now examine. It is these codes that enable us to use OSWRCH to draw graphics on the screen, execute COLOUR and GCOL commmands, define characters, and control the 6845 chip - which controls the video display of the BBC Micro.
Writing characters to the screen or elsewhere via the OSWRCH routine is often referred to as writing to the 'VDU drivers'. The ASCII Control Code Table shows the effects of the character codes between 0 and 31 when they are sent to the VDU drivers. As you can see, they enable us to do anything via the OS in our machine code graphics routines that we can do in BASIC.
ASCII Control Codes Table:
Code Extra Bytes Description
Needed
---- ----------- -----------
0 0 Does nothing
1 1 Sends the next character to the printer only
2 0 Turns the printer on
3 0 Turns the printer off
4 0 Writes text to the text cursor
5 0 Writes text to the graphics cursor
6 0 Allows VDU drivers to write characters to output
stream
7 0 Generates a short tone
8 0 Moves cursor one space left
9 0 Moves cursor one space right
10 0 Moves cursor one space down
11 0 Moves cursor one space up
12 0 Clears the text area of the screen
13 0 Returns cursor to beginning of current line
14 0 Paged mode turned on
15 0 Paged mode turned off
16 0 Clears the graphics area of screen
17 1 Sets text colour to colour whose code is the
following byte
18 2 Does a GCOL with the next two bytes to be sent to
the drivers. For example, sending 18,0,3 would per-
form a GCOL 0,3 command
19 5 Defines the logical colours. See BBC user guide
20 0 Returns logical colours to default values
21 0 Does not allow characters to be written to output
stream by the VDU drivers
22 1 Sets screen mode to mode of following byte. Sending
22 and 7 will enable Mode 7. However, HIMEM is not
altered
23 9 Sends commands to the 6845 chip; programs user-
generated characters
24 8 Defines a graphics window
25 5 Performs PLOT command
26 0 Sets default text and graphics window
27 0 No effect
28 4 Sets up a text window
29 4 Sets the graphics origin
30 0 Returns the text cursor to top left of screen
31 2 Puts text cursor to the x,y position in the two
bytes following. For example, sending 31,10,10 will
set the text cursor position 10,10 on the screen
Graphics Via OSWRCH
All the usual graphics commands are available to us via the OSWRCH routines. Our second example program, given below, will draw a red line on the screen:
10 MODE 1 20 FOR I%=0 TO 2 STEP 2 30 P%=&C00 40 [OPT I% 50 LDA #18 60 JSR &FFEE 70 LDA #0 80 JSR &FFEE 90 LDA #1 100 JSR &FFEE 110 120 LDA #25 130 JSR &FFEE 140 LDA #5 150 JSR &FFEE 160 LDA #0 170 JSR &FFEE 180 LDA #100 190 JSR &FFEE 200 LDA #0 210 JSR &FFEE 220 LDA #100 230 JSR &FFEE 240 RTS 250 ]:NEXT 260 CALL &C00
Lines 50 to 100 of the program execute a GCOL 0,1 command, setting the colour of the line to red. Lines 120 to 240 execute a PLOT 5,100,100 command, which is the same as a DRAW 100,100 command. Line 140 sends the PLOT type (5 in this case) to the VDU drivers, followed by a two-byte x co-ordinate, low byte first, and then a two-byte y co-ordinate, low byte first. A MOVE command can be executed by replacing the 5 with a 4 (MOVE is simply a PLOT 4,x,y command). Other graphics operations - such as the PLOT 85 command for drawing triangles - are also accessible by these means. One important point to remember about sending PLOT commands to the VDU drivers is that they expect five bytes after the value 25 sent as the first byte; if these bytes are not received, then strange things can happen. This applies to all VDU driver operations that require more than one byte to be sent.
VDU23 another very versatile VDU driver command. It is used to define user-generated characters. For example, VDU23,224,255,255,255,255,255,255,255,255 will define the character 224 (usually undefined) as a solid block. Characters 224 to 255 in Modes 0 to 6 can be redefined by the user with this command. Indeed, using the VDU23 command in conjunction with one of the OSBYTE calls enables the user to redefine other characters in the character set. Any VDU23 calls that are not recognised by the OS - such sa VDU23,0,... - are passed through a special vector at &226 and &227. By changing the address contained in this vector, you can add your own VDU23 command routines.
A more advanced use for the VDU23 command is to enable the programmer to access the 6845 video controller chip. VDU23 commands take the form:
VDU23,0,register,value,0,0,0,0,0,0
where 'register' is the 6845 register to which you want to write, and 'value' is the value to be written to the 6845. As an example of the use of VDU23 in this way, the following program alters two of the 6845 registers: they tell the chip which area of the computer's memory is to be used as video RAM to address &0000. This shows the BBC OS workspace on the screen, and various interesting effects can be seen. Try adding a few lines of code, dimensioning some arrays etc. The routine is written in BASIC but will easily convert to assembler:
10 MODE 0 20 VDU23,0,12,0,0,0,0,0,0,0 30 VDU23,0,13,0,0,0,0,0,0,0 40 VDU28,0,10,30,0:REM set up text window 50 CLS
There are two other OS calls that are related to OSWRCH. These are OSNEWL and OSASCI. OSNEWL, when called at &FFE7, writes a line feed and a carriage return to the screen. OSASCI, called at &FFE3, is a variant on OSWRCH, and is useful for text handling. When a character 13 is written via this call, a line feed, or character 10, is also written to the output stream. This should *not* be used, therefore, if you are writing graphics commands to the VDU drivers, since an extra CHR$10 might be generated, thus causing confusion.
Finally, *SPOOL and *EXEC are two commands that enable output and input to the currently selected filing system. *EXEC filename will cause a file with the appropriate name to be opened, if present, and its contents read in, as if from the keyboard. *SPOOL writes characters to the file named in the command, as if the characters were being written to the output stream.