Operating Systems 03

By Chris Dewhurst

Originally published in EUG #61
This article appeared in the HOME COMPUTER ADVANCED COURSE MAGAZINE (c) 1984

Our introduction to the BBC operating system concludes with this instalment. We take a detailed look at the use of vectors, and investigate how the OS enables us to interact with the computer via the keyboard and VDU.

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

  1. The first character input will be stored at &C00, the second at &C01, and so on.
  2. 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.
  3. Only characters with ASCII codes between 32 (the Space character) and 96 (the £ character) will be accepted; others will be ignored.
As you can see, the call enables us to screen out unwanted characters in the input. When the routine is exited, the status of the C flag informs us what caused the termination of the routine. If C=1, then ESCAPE was pressed. If C=0, then RETURN terminated the entry of characters and the Y register holds the length of the string entered, including the carriage return ASCII value added to the end of the string by the pressing of RETURN. Remember that you can use this routine on either of the input streams selected by *FX2 or its machine code equivalent.

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
----  -----------  -----------
   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
   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
  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
       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:


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.