Bottom     Previous     Contents

Chapter Ten
The MOS - A Soft Machine

Of the two pieces of system software stored in ROM (BBC BASIC and the MOS Machine Operating System) BASIC gets by far the greater amount of attention and praise. This is only to be expected because BBC BASIC is a fairly advanced dialect of a very well known language. However, BBC BASIC relies very heavily on the software support of the MOS and many of the actions that appear to be part of BASIC are simply passed over as a request for action to the MOS. The MOS is also important in that it is the only piece of 'fixed' ROM software in the BBC Micro. For example, you might replace the BASIC ROM by another language but the new language would still use the MOS as support software.
Some of the details of the workings of the MOS have already been described in The BBC Micro: An Expert Guide and the aim of this chapter is to enlarge on some of the ideas presented in this earlier book. The MOS is a highly structured piece of software and there is much that can be learned from it about the way that the BBC Micro works. The first part of this chapter builds up a general picture of the different sections of the MOS and the tasks that they perform. The final part presents a short project that makes use of a very special feature of the BBC Micro, 'event handling', to produce a clock display that allows you to run another program while still displaying the time.

What is a machine operating system?

The reason why there is, in general, so little comment on the MOS probably has something to do with it being an operating system. The purpose of implementation of a language such as BASIC is obvious, but the purpose of an operating system is not so clear. The quality of a dialect of BASIC is therefore easy to judge while it is difficult to set a standard for an operating system. Tradition has it that the purpose of an operating system is to make the facilities of a machine available to other programs and ultimately the user, and this is indeed what the MOS does. However. if an operating system offers up a facility in such a way that it is difficult to use, does the fault lie in the operating system or the hardware of the machine? You might think that the answer to this question depends on the particular case; sometimes the software would beat fault and sometimes the hardware. The surprising thing is that it is always the software! No matter how horrible the hardware is it is possible for the software contained within the operating system to deliver it to the user in a way that is easy to use. The only thing that you can blame the hardware for is lack of speed and this might make the software's job pointless - an easy-to-use but incredibly slow system is not something that anyone wants! An ideal operating system should, so to speak, 'tame the machine's hardware' so that other software can concentrate on putting it to good use.
Operating systems are not often considered an important factor in personal computing and machine designers have often overlooked the need for good operating systems and concentrated on the quality of the BASIC. This has forced BASIC to grow extensions that look after whatever extra hardware that the machine has. An example of this approach at its most extreme can be seen in the APPLE II's so-called Disk Operating System or DOS. This is so much an afterthought to APPLESOFT BASIC that it is difficult to see it as a separate piece of software! The BBC Micro was possibly the first machine fully to use an operating system both to make facilities available and to enhance the performance of the hardware. The MOS was designed as an essential and integral part ofthe BBC Micro and without it the hardware would look distinctly incomplete! Indeed the MOS is best regarded as a software extension of the hardware and in this sense the BBC Micro is very much a creation of software. In other words, it can be viewed as a 'soft machine'.

The structure of the MOS

The main design decision that seems to have given rise to the MOS is that all of the machine's I/O would be handled by it and it alone. In this sense the MOS can be thought of as a layer of software that separates other system software, such as BASIC, from the hardware (see Fig. 10.1). This separation makes it possible to enable languages such as BASIC to be hardware independent. At first it may be difficult to see any payoffs for the user of this hardware independence but it is, for example, responsible for the uniform way that files are handled no matter what the file device concerned tape, disk, network or telesoftware (see Chapter Six). As far as file devices are concerned, there is just one set of I/O commands and the variability in the storage device is 'absorbed' by the filing system software.
Miscellaneous and character-oriented devices, such as the sound generator and the serial interface, are always difficult to build into an operating system in a regular way but the BBC MOS does its best. The text and graphics display is such an important part of the machine that it is given a section of the MOS all to itself the VDU drivers. Other I/O) devices are combined with miscellaneous operations and dealt with in two classes those that require only a small amount of information to be passed and those that require a large or variable amount of information to be passed. The reason for this division is entirely practical because it enables a simple method of communicating with the MOS to be used wherever possible.


Fig. 10.1. The flow of data.

The MOS also acts as a sort of 'supervisor' program for any other ROMs that might be present in the sideways ROM sockets. For example, when the machine is first switched on the MOS selects, or 'pages', the ROM that is in the numbered ROM socket with the highest number (see later). A more important example of the role of the MOS in ROM paging is in the processing of 'service' requests. When the ROM currently in use makes a call to the operating system (via OSBYTE, for example) to perform some I/O Oor any other task the MOS will attempt to carry out the request. However, if the M OS doesn't recognise the request as something it is capable of dealing with, it will ask each of the sideways ROMs in turn if they can deal with the request. Thus the MOS is responsible for implementing a fairly sophisticated paging system that can effectively extend the BBC Micro's memory space to well over the 64K of memory that the 6502 can address directly.
The final, and perhaps the least obvious, action of the MOS is to look after interrupts. Put simply, an interrupt is a signal that stops a computer from carrying out its current task, switches its attention to another task and then returns it to its current task as if the interruption had never occurred. Thus interrupts can be used to give a computer the appearance of doing more than one thing at a time by repeatedly and very rapidly switching between a number of tasks. The BBC Micro is an advanced machine in that interrupts are an essential part of the way it works not, as in the case of so many machines, an afterthought added to control a special I/O device or provide a clock. An interrupt-driven machine has a very special 'feel' about it for both programmers and users. In particular, programmers have to be aware ofthe potential of a machine that runs under interrupts and of the consequent problems. Apart from providing the real time clock in the pseudo variable TIME, the interrupts are used to service and maintain the extensive system of I/O buffers and queues and provide information to user programs about when certain events have occurred. To get the best from your BBC Micro it is very important to understand the nature of an 'interrupt driven' machine. To this end the final part of this chapter gives an example of how interrupts can be used within a program.


Fig. 10.2. The structure of the MOS.

The structure of the MOS as described above can be seen in Fig. 10.2, along with the names of the machine code subroutines that BASIC and other software use to communicate with each section. Notice that although the interrupt and event handler looks as though it has no way of letting either BASIC or a user program gain access to it, this is not the case, as will be explained later. As with any piece of real software there are always odds and ends that don't fit into the overall classification and the MOS is no exception. For example, there is a 'command line interpreter' which while not essential does make the MOS easier to use. Now that the structure of the MOS is clear it is time to look more closely at each of its parts.

The file system

In most computer systems it is useful to distinguish two types of I/O devices those that work in terms of named files and those that work by transferring small units of data one after the other. File devices include the well known cassette recorder and floppy disks of all kinds (Chapter Six has already described file handling as it applies to these two types of device). However, for the BBC Micro the range of file devices extends to exotic systems such as networks, telesoftware and ROM cartridges. Such diverse systems might lead you to believe that a different method of handling is appropriate and indeed necessary for each one. This is not so! As already mentioned at the start of Chapter Seven, the idea of a file and the operations that manipulate it are independent of the actual physical device used to store it. The physical device affects the speed of storage and retrieval (and very occasionally it may not allow a particular operation) but it doesn't alter the principle underlying handling files.
The MOS uses a standard set of six machine code subroutines for all file operations, no matter what type of device is in use. Two of the subroutines, OSFIND and OSARGS, are concerned with general file operations such as 'opening', 'closing', 'deleting', etc. The remaining four are used to read and write files. OSBGET and OSBPUT are used to read and write a single byte from and to a file that is already open. OSFILE reads or writes an entire file in one operation. The final subroutine OSGBPB will write or read a variable sized block of bytes to the file this can only be used with devices that can sensibly support random access such as the disk system.
Each one of these subroutines is used by referring to a fixed location in the MOS ROM but, as these locations immediately transfer control to jump vectors in RAM, the position of the code that actually carries out the operation can easily be changed. In this way the code used when you call one of the subroutines can be made appropriate for the currently selected filing system tape, disk, network, etc. These subroutines are used by BASIC to implement its file I/O and you should be able to recognise the correspondence between the BASIC commands GET, PUT and LOAD/ SAVE with the MOS subroutines OSBGET, OSBPUT and OSFILE. Of course the more familiar INPUT and PRINT make repeated use of the MOS subroutines to handle their multiple data transfers. The use of these machine code subroutines has already been described with reference to tape and disk in Chapter Six.

The VDU driver

The VDU driver is responsible for performing all of the text and graphics operations provided by the BBC Micro. If you are familiar with the hardware details of how the BBC Micro produces its TV display (see The BBC Micro: An Expert Guide for this hardware information) you will realise that the VDU driver has a lot to do simply to make a character appear on the screen. As well as this fundamental job, it also has to provide the colour control, implement the graphics commands, look after the scrolling, implement and maintain the text and graphics windows and look after just about every other feature of the display. With so many different things to do you might expect that the VDU driver would consist of a great many different subroutines, each with a different name and method of use. As in the case of the file system, it is possible to make things simpler by careful planning and observing how information is normally generated and sent to a video display. As text is usually sent to the display in the form of a stream of ASCII codes it makes sense to continue to use the same method to communicate all the data to the VDU driver. In other words, the VDU driver can use a single entry point as long as it examines the stream of ASCII codes that is being sent to it for special 'control codes'.
The single entry point is OSWRCH and the ASCII codes are extended to include all of the text and graphics operations. For example, if the VDU driver detects ASCII code 12 then it will clear thetext screen, a code of 1 6 will clear the graphics screen and so on. Sometimes the amount of information needed for a text or graphics operation is greater than a single ASCII code can convey. In this case a sequence of ASCII codes is required. For example, following ASCII code 31 the next two codes are not interpreted in the usual way but are taken to be the new X and Y position of the text cursor.
The familiar BBC BASIC text and graphics commands are implemented by sending the appropriate ASCII codes to the VDU driver. For example, the command CLS results in the code 12 being sent to the VDU driver. In this sense the BASIC statements:

CLS
PRINT CHR$(12);

and

VDU 12

all achieve exactly the same results - they send code 12 to the VDU driver and so clear the screen and are therefore the same! TAB(X,Y) first sends code 31 and then two codes whose values are given by X and Y respectively. In other words, BBC BASIC doesn't manipulate directly the contents of the RAM that stores the screen data. Instead it converts its requirements into the appropriate sequence of ASCII codes and sends them to the VDU driver which then changes the screen data. All programs running on the BBC Micro, whether in BBC BASIC or machine code,should use the VDU driver and avoid direct manipulation of the screen memory if at all possible.
For the sake of convenience, it is useful to have three entry points to the VDU driver; OSWRCH, which sends the code stored in the A register to the VDU driver; OSASCI, which inspects the code and, if it is a carriage return, sends a new fine code to the VDU driver, and finally. OSNEWL, which is an entry point that automatically sends a new line followed by a carriage return code. Each of these subroutines is entered via a fixed address within ROM which then immediately transfers control to a routine whose address is stored in RAM. This makes it possible to intercept all output going to the screen by changing the contents of the RAM vector and to replace or alter OSWRCH, etc. An example of this can be found in the trace program given in Chapter Nine.
The power of this simple system of using a stream of ASCII codes to control the complex text and graphics operations takes some time to appreciate. One of its advantages is that both the BASIC programmer and the assembly language programmer control the display in exactly the same way. The sequence of codes that the BASIC programmer sends to the VDU driver, using, say, the VDU command, is exactly the same as the set of codes that the assembly language programmer has to send to the VDU driver by calling OSWRCH directly.

Miscellaneous I/O

There are two machine code subroutines OSBYTE and OSWORD which control a wide range of MOS activities. Exactly what OSBYTE and OSWORD do is controlled by the code stored in the A register before they are called. Once again the programmer is spared from having to deal with a large number of separate and specialised subroutines by the simple expedient of using a single entry point and an action code. OSBYTE is made available to BASIC programmers in two ways. Firstly, it is used to implement many BASIC commands, For example, the INKEY function calls OSBYTE to read the keyboard. Secondly, many OSBYTE actions are available via the familiar *FX command. Only OSBYTE actions that do not return information to the program can be used via the *FX command, because although the * FX command can transfer data to OSBYTE it has no mechanism for returning it. The command *FX a,x,y places the value 'a' in the A register, 'x' in the X register and 'y' in the Y register and then calls OSBYTE. In this sense it is exactly equivalent to the assembly language:

LDA #a
LDX #x
LDY #y
JSR OSBYTE

If 'x' or 'y' are omitted the command will automatically load the appropriate registers with zero.

The OSWORD subroutine works in a very similar way to OSBYTE but, as the amount of data involved is more than just the A, X and Y registers can hold, the *FX or an equivalent cannot be used to access OSWORD from BASIC. The only access to OSWORD that a BASIC programmer has is indirect and via the BASIC commands that call OSWORD to implement their operations. For example, the SOUND command sets up a data block of information about pitch, volume, etc., then it loads the A register with 7, sets the X and Y registers to point at the data block and jumps to OSWORD which does the real work of setting up the sound generator. OSWORD calls are listed in Section 43 of the User Guide.
The *FX commands are also well documented in the BBC Micro's manual and so it isn't worth repeating the list here. However, there area number of OSBYTE and OSWORD calls that are useful from BASIC but no direct command is provided to access them. For example, the OSWORD call with A set to 10 will return the dot definition of any character. The ASCII code of the character is stored in the first location of a data block and the dot pattern is stored in the following eight bytes of the block when OSWORD returns. Although this OSWORD call isn't available in BASIC it is easy to write a procedure to implement it:

1000 DEF PROCshape(CODE%)
1010 ?&600=CODE%
1020 A%=10
1030 Y%=&06
1040 X%=&00
1050 A%=USR(&FFF1)
1060 FOR A%=1 TO 8
1070 DOT%(A%)=A%?&600
1080 NEXT A%
1090 ENDPROC

The eight bytes of the dot pattern for the character corresponding to CHR$(CODE%) are returned in the array DOT% which must be dimensioned in the main program before PROCshape is used. The area of memory starting at &600 is used for the data block because it is unlikely to be in use for anything else. (It is normally used for the parameter block for a CALL statement.) As another example the OSWORD call with A set to 11 returns the logical to physical colour assignment asset by any previous VDU 19 commands. This is also not available as a direct BASIC command but it is easy to add it as a function:

2000 DEF FNphyscol(L%)
2010 ?&600=L%
2020 A%=11
2030 Y%=&06
2040 X%=&00
2050 A%=USR(&FFF1)
2060 =?&601

This will return the physical colour code assigned to the logical colour code stored in L%. You should be able to see the similarity between this function and the previous procedure.
The final two I/O procedures, OSRDCH and (for a second time) OSWRCH, are used to read and write single bytes of data to any of the character-oriented devices. Normally OSRDCH will return the ASCII code of any key pressed on the keyboard and OSWRCH will send the ASCII code of a character to the VDU driver. This is because the keyboard and the screen are the default I/O devices. It is possible to change this default selection using the OSBYTE subroutine and in this sense OSRDCH and OSWRCH are general purpose single character input and output routines. For example, following *FX 3,3 OSWRCH sends a character to the printer port and following *FX 2,1 OSRDCH gets characters from the serial port. (See the short example in the section on the interrupt and event handler.)

ROM paging

The ROM paging activities of the BBC Micro are unlikely to be of practical interest unless you plan to produce your own software in ROM. However, the general method used by the MOS to handle paging is interesting for its own sake and will be described in this section. If you do feel up to tackling the difficult and time-consuming problem of putting software into ROM then you will find all of the technical details that you need in a booklet entitled BBC Micro ROM Paging Systems Explained, produced by Watford Electronics (35/37 Cardiff Road, Watford, Herts. They can also supply all the extra hardware that you will need).
Although the 6502 inside the BBC Micro can only address 64K of memory directly, this has been extended by allowing any one of a possible 16 ROMs to occupy the 16K region from &8000 to &C000. You can think of these 16 ROMs as the pages of a book and at any one time the book can only be opened to reveal one of the pages. This method of sharing a limited amount of address space is usually called paging. From a hardware point of view the BBC Micro's paging system is relatively simple. If we assume that there are 16 ROM sockets numbered 0 to 15 then to select (or 'page in') the ROM in socket x all we have to do is to write the value of'x' to the ROM page register at &FE30. An unmodified BBC Micro has only four paged ROM sockets on the main PCB but this number can easily be extended to the full 16 sockets by the use of an expander board. (Some expander boards will even allow you to install 16K of RAM in the address range &8000 to &C000 and this makes ROM software much easier to develop.)
The hardware side of ROM paging is indeed very simple but the real difficulty inherent in any paging system is how to make sure that the required page is enabled when und only when it is needed this is clearly a software problem. To be more precise it is a problem that the MOS has to solve. The 16K of software that constitutes the MOS is always in position at &C000 to &FFFF no matter which of the paged ROMs is selected. This makes it the ideal (in fact the only!) candidate for looking after the paging process.
The simplest paging action that the MOS carries out is detecting which ROM sockets actually have a valid ROM installed and then paging the one with the highest number into the address space. This happens when the machine is first switched on or when BREAK is pressed. If you are using an unmodified BBC Micro then it might be of use to know that the ROM sockets are numbered from 12 on the left-hand side making the right-most socket number 15. (The socket on the far left of the row of five is not a page ROM socket but holds the MOS ROM instead.) In other words, this paging action makes sure that when the machine is first switched on, or when BREAK is pressed, the ROM in the right-most socket is enabled this is usually BASIC but it could equally well be any other language or, say, a word processor.
For a ROM to be detected its first few bytes must conform to the following format:

Offset (from &8000) Size (in bytes)
0 3 JMP language
3 3 JMP service
6 1 ROM type
7 1 Length of information area.
8 1 Version number (in binary).
9 as required Title string ending with 00.
- as required Version string ending with 00.
- as required Copyright string starting with '(C)' and ending with 00.
- 4 Tube relocation address.

The most interesting of these entries are the JMP language and JMP service instructions. Each ROM can consist of two parts a language part and a service part and these two instructions are used by the MOS to enter either part as required. The language part of a ROM is characterised by being a candidate for transfer over the Tube to run on a second processor and for this reason it must avoid any direct reference to the main processor's I/O locations it must use the MOS for all I/O operations, A service part of a ROM is not a candidate for transfer over the Tube and as such it can make direct use of the main processor's I/O locations and generally get involved with the hardware. Service ROMs are usually concerned with providing additional software support to other programs - i.e. they provide a service to other programs. For example, the Acorn disk operating system is a service ROM. On the other hand language ROMs contain programs like the BASIC interpreter or word processors that make use of service ROMs to handle the hardware. This distinction isn't clear cut, however, and most ROMs have both a service and a language entry. The rest of the entries are mainly about describing the nature of the ROM. The type byte of each valid ROM that is detected is stored in a table, the address of which can be found by using OSBYTE call 170. (The start of the table is returned in the X and Y registers. )
If the selection of a single ROM from a number of possibilities was the only sort of paging that the MOS carried out then there would be little advantage in using it! In fact the ROM paging system is used a great many times during the normal running of a program. The language part of any ROM can be entered at any time by using a *FX 142, ROM number command or its OSBYTE equivalent. This OSBYTE command can be issued by any of the paged ROMs or the. MOS itself. If it comes from another paged ROM, control is first passed to the MOS which then performs the necessary paging operation.
In the same way, the service part of a ROM can be entered using *FX 143 or its equivalent OSBYTE call. However, in this case the call is somewhat more complicated. The full form of the *FX command is:

*FX 143,call number,parameter

where 'call number' describes the reason for the calland 'parameter' supplies any additional information that is necessary. Notice that unlike the language call this service call does not name the particular ROM socket number that should deal with the call. The reason for this is that this number will usually be unknown to the MOS and instead it will page in each of the ROMs in turn and enter the service part with the 'call number' in the A register. In this way the service part of each ROM is given the chance to either take notice of or act on the call. The effects of most of the call numbers are difficult to explain exactly but the following table gives a summary.

Call Number Action
0 NOP - no operation - if a service ROM acts upon a call and needs to make sure it will be ignored by the remaining ROMs it can set A to zero before returning to the operating system.
1 Work space required each ROM may state its needs for working memory.
2 Private space required each ROM may state its need for private, i.e. non shareable, work space.
3 Auto-boot when BREAK is pressed each ROM is given a chance to test the keyboard to see if its 'start up' key is pressed. If it is then the ROM may transfer control to its language section or run any other program it likes.
4 Command not recognised by MOS. If a command starting with '*' is not one that the MOS recognises it offers it to each of the ROMs in the hope that one of them will recognise and act on it. On entry Y,(&F2) points to the start of the command.
5 IRQ not recognised. The MOS cannot identify the source of an IRQ interrupt and asks each ROM in turn to try to identify it. If a ROM is not using any interrupts then it simply returns control to the MOS.
6 BRK a BRK instruction has to be executed i.e. the system is in an error condition.
7 OSBYTE call not recognised. An OSBYTE call that the MOS doesn't recognise is passed to each of the ROMs to see if it is one that they can handle. The OSBYTE parameters A,X and Y are stored in &EF,&F0 and &F1 respectively on entry and are also used to return any results.
8 OSWORD call not recognised. As in the case of an unrecognised OSBYTE call each ROM is given a chance to process the unrecognised OSWORD call. Notice that OSWORD calls in the range &80 to &FF indirect via USRV rather than cause ROM paging.
9 HELP information required each ROM should print its title line, etc.
A Claim main work space a ROM wishes to use the work space.
B NMI released.
C NMI claimed.
D Initialise *ROM filing system.
E Return a single data byte for *ROM filing system.
F Claim MOS indirection vectors.
10 Filing system is about to close *SPOOL and *EXEC files.
11 Character set about to implode/explode.
12 Initialise filing system.
FE Tube post initialisation.
FF Tube hardware present.

Many of these calls simply inform any ROMs that might be active of a system condition but four of them effectively extend the facilities offered by the MOS. Call 4 extends the range of '*' commands that are recognised, call 5 extends the range of interrupts that are recognised (seethe next section), call 7 extends OSBYTE and call 8 extends OSWORD. For example, suppose you have written a new word processor and you want to call it 'BWORD'. If you included a service entry that recognised the letters of'BWORD' then you could be paged in by the command '*BWORD'. When the MOS encounters this command it would find that it wasn't one that it recognised and it would begin to page in the ROMs one by one and enter their service section with call number 4 unrecognised command. Each ROM would then check to see if they recognised the command. Of course, if you have chosen the name 'BWORD' carefully only the service section of your word processor ROM will recognise it. To get the word processor going the service section of your ROM would call the MOS using *FX 142,ROM number which would then enter your ROM at the language entry point.
As a more simple example, suppose your new ROM implements a command that makes a specific sound effect and you want it to be added to the MOS as OSBYTE &A0. When the MOS is entered via an OSBYTE call with a code of &A0 it cannot identify what action to take and so it starts to page each ROM in turn using call number 7. Each ROM will inspect the unknown OSBYTE call and return control to the MOS if it too fails to recognise it. Of course when your new ROM is paged it will recognise the OSBYTE call and so will proceed to make the required sound effect. Finally your ROM should set A to zero and return to the MOS so that none of the remaining ROMs will even bother to examine the call.
There is much more to say about how the MOS ROM paging mechanism can extend the MOS. In particular, there is the whole subject of filing systems and how new filing systems can be written so as to integrate with the existing software but all this is beyond the scope of this book. If you are interested then you will find the extra details that you need in the booklet from Watford Electronics mentioned at the start of this section and this, coupled with the overall description given here, should be sufficient for you to implement your own ROM software.

The interrupt and event handler

While the BBC Micro is running interrupts are generated every hundredth of a second by one of the timers inside VIA A. Each time one of these regular interrupts is received the interrupt handler, whose address is stored at &204, is called. This updates the pseudo variable TIME and then checks to see if anything else needs attention. For example, it is responsible for altering the parameters of the sound generator for the duration of a sound controlled by an envelope. It is this action that makes the BBC Micro's sound generator so powerful. From a hardware point of view it is not at all impressive but when it is added to the frequent and periodic attention that the interrupt handler provides it is as good as more complex hardware and far more flexible.
The interrupt handler also looks after the system of queues which are such an important feature of I/O through the MOS. Using the sound generator as an example for the second time, it is easy to see that at each interrupt the interrupt handler has to check to see if the current sound has reached its specified duration or not. If it hasn't then the sound generator is left alone. If it has then the sound queue associated with that channel is inspected. If it is empty then the channel is switched off, otherwise the parameters stored in the queue are used to start the next tone and the queue is moved up by one. This interrupt servicing of the sound queue is responsible for the BBC Micro apparently being able to do two things at once - run a BASIC program and generate a sound. The printer and serial ports are both associated with queues or buffers that are serviced by the interrupt handler and, as in the case of the sound generator, this greatly enhances the performance of the entire system. For example, it is quite possible for a BASIC program to print out a large quantity of data while getting on with another job. All it has to do is send data to the printer buffer until it detects, using ADVAL(-4), that it is full. Then it can get on with another job and the interrupt handler will automatically send a character at a time to the printer, at the rate that the printer can accept. Of course, if left in this state all that would have been printed is a single buffer full of data but if, while carrying out its other jobs, the BASIC program occasionally checks to see if the buffer is once again empty (using ADVAL(-4)) and transfers enough data to keep it topped up, any amount of data can be printed without stopping the BASIC program unnecessarily. As an illustration of this method the following short program will print powers of two on the printer and a sequence of integers on the screen.

10 I=0
20 J=2
30 IF ADVAL(-4)=63 THEN PROCprint(J)
40 I=I+1
50 PRINT I
60 GOTO 30

1000 DEF PROCprint(J)
1010 REPEAT
1020 VDU 2,21:PRlNT J:VDU 6,3
1030 J=FNupdate(J)
1040 UNTIL ADVAL(-4)<10
1050 ENDPROC

2000 DEF FNupdate(J)
2010 =J*J

The first part of the program, consisting of lines 10,40,50 and 60, is easily recognised as a simple infinite counting loop. Line 30, however, tests to see if the printer buffer is completely empty. If it is then PROCprint is called to refill it. This ensures that, as the values of I are printed on the screen, the printer is periodically given something to keep it busy. PROCprint simply sends as many values of J as the printer buffer can hold and then returns control to the main program. Notice that the REPEAT loop, lines 1010 to 1040, comes to an end ten characters before the buffer is full. The reason for this is that the PRINT statement adds a number of characters to the buffer each time it is executed and if the buffer became full during this print statement the program would have to wait until space was made available. This would of course defeat the object of the program. Also notice that following each addition to the buffer FNupdate is called to provide a new value for J. In practice FNupdate would usually be a function to read single characters from a text tile stored on disk. so allowing a file to be listed while the program gets on with something else. You might be a little worried by line 1020 as it uses VDU 21 and VDU 6 to disable and enable screen output respectively, rather than the *FX called mentioned in the section on miscellaneous I/O. The reason for this is that *FX 3 appears not to work when the printer is the only selected output device (i.e. *FX 3,2 will not select the printer). This presents no real difficulty as VDU codes 21 and 6 can be used to send output to the printer without affecting the screen. It is quite possible to write a machine code version of the above program that would print the contents of a file while an entirely different program was being used. How this could be done will be easier to see after the following example.

A background clock

Interrupts are such an important part of the BBC Micro that it is worth giving an example of a complete project that makes use of this facility. The aim of the project is to add a 'background clock' that will display hours, minutes and seconds on the screen no matter what the BBC Micro is doing and without interfering with the running of any other programs. The screen display part of this problem has already been solved in connection with the trace program described in Chapter Nine and the only really new element is how to update the display periodically. Perhaps the most obvious method of updating a clock is to use the regular interrupts to enter a routine that increments the current time. However, interrupts occur every hundredth of a second and the background clock only has to 'tick' once every second. It would be possible to count one hundred interrupts and then increment the time but in the case of the BBC Micro this would be a complicated way of doing things. As well as a five byte timer that is used to provide the pseudo variable TIME, the MOS also maintains a separate 'interval timer' that is incremented with each interrupt. This interval timer can be read using OSWORD 3 and set to any given value using OSWORD 4. For this particular project the most important feature of the interval timer is that it can be used to cause a periodic event that repeats after any given interval. If the 'interval timer crossing zero' event is enabled using *FX 14,6 then an event will occur each time the event timer reaches a count of zero. As the event timer counts up to &FFFFFFFFFF and then resets itself to zero, the time between these events can be set by storing an appropriate value in the event timer. In this case we need an event to occur after one second, i.e. after one hundred interrupts, so the value that should be stored in the interval counter is &FFFFFFFFFF-100 (because this will make the interval timer reset to zero after being incremented 100 times).
Now that we have a method of making the clock 'tick' every second the rest of the project is mamly about routine details such as how the data should be represented, etc. (The rest of this discussion assumes that you know something about simple binary and BCD) representation - if this is not the case then you will find these ideas dealt with at length in Chapter Twelve.) Obviously we are going to have to store a two digit value for each of hours, minutes and seconds, increment them in such a way that a count of 60 seconds adds one to the minutes and a count of 60 minutes adds one to the hours. We could use simple binary to store the values but this would make printing them out as decimal digits rather difficult. Instead it makes sense to use a BCD (Binary Coded Decimal) representation. BCD is explained more fully in Chapter Twelve but essentially BCD uses four bits to represent a single decimal digit in the range 0 to 9. Thus a single memory location can store a pair of BCD digits and this is particularly convenient for our application as a single memory location can be used for the two digit hours, minutes and seconds.
After so much discussion, writing the main program is easy:

3000 .TICK%

3010 ]PROCsave:[OPT PASS

3020 JSR UPDATE%

3030 JSR DISPLAY%

3040 JSR RESET%

3045 ]PROCrestore:[OPT PASS

3050 RTS

PROCsave and PROCrestore are the two macros introduced as part of the trace program in Chapter Nine which save and restore all of the 6502 registers - this has to be done for all but the simplest event handling routine as the register values have to be preserved. UPDATE% is simply a routine to increment the time. DISPLAY% prints the time at the top of the screen and then restores the cursor's position. Finally RESET% sets the interval timer's value so that an event will occur again after one second has passed.
The second stage of refinement is fairly easy. The UPDATE% routine simply adds one to SEC% and checks to see if it has reached 60. If it has then it is zeroed and one is added to MIN% which is also checked to see if it has reached 60. If it has then it too is zeroed and one is added to HOUR% which is then checked to see if it has reached 24 (the background clock is a 24 hour clock!). If it has then it is also zeroed. The DISPLAY%, routine is essentially the same as the routine used in the trace program to alter the cursor position. The only difference is that it now calls DISPNUM% to print the time before returning the cursor to its original position. RESET% simply uses a call to OSWORD 4 to set the interval timer and needs no comment.
The third and fourth stages of refinement are now quickly implemented as only DISPNUM% remains undefined. DISPNUM% is straightforward, if a little tedious in that it uses a pair of subroutines, L_NUM% and RNUM%, to convert the left (i.e. bit 7 to bit 4) and the right (i.e. bit 3 to bit 0) digits of a BCD number into valid ASCII digits and then uses OSWRCH to print the results.
The only part of the program that remains is a routine that will alter the 'event vector' at &220 to contain the address of the start of theclock program and enable the interval timer to reach zero event. This can be seen in the complete version of the program given below as INIT%.

10 DIM CODE% 500

20 PROCasm(CODE%)

30 CALL INIT%

40 STOP

1000 DEF PROCasm(START%)

1005 FOR PASS=0 TO 3 STEP 3

1010 P%=START%

1020 PROCprog

1030 NEXT PASS

1040 PRINT

1050 PRINT P%-START%;" Bytes"

1060 ENDPROC

2000 DEF PROCprog

2010 OSBYTE%=&FFF4

2020 OSWRCH%=&FFEE

2030 EVNTV%=&220

2040 OSWORD%=&FFF1

2990 [OPT PASS

3000 .TICK%

3010 ]PROCsave:[OPT PASS

3020 JSR UPDATE%

3030 JSR DISPLAY%

3040 JSR RESET%

3045 ]PROCrestore:[OPT PASS

3050 RTS

3060 \

3070 .UPDATE% SED

3080 CLC

3090 LDA SEC%

3100 ADC #&01

3110 CMP #&60

3120 BNE UP2%

3130 CLC

3140 LDA MIN%

3150 ADC #&01

3160 CMP #&60

3170 BNE UP1%

3180 CLC

3190 LDA HOUR%

3200 ADC #&01

3210 CMP #&24

3220 BNE UP3%

3230 LDA #&00

3240 .UP3% STA HOUR%

3250 LDA #0

3260 .UP1% STA MIN%

3270 LDA #0

3280 .UP2% STA SEC%

3290 CLD

3300 RTS

3310 \

3320 .DISPLAY% LDA #134

3330 JSR OSBYTE%

3340 STX XTEMP%

3350 STY YTEMP%

3360 LDA #31

3370 JSR OSWRCH

3380 LDA #20

3390 JSR OSWRCH%

3400 LDA #0

3410 JSR OSWRCH%

3420 JSR DISPNUM%

3430 LDA #31

3440 JSR OSWRCH%

3450 LDA XTEMP%

3460 JSR OSWRCH%

3470 LDA YTEMP%

3480 JSR OSWRCH%

3490 RTS

3500 \

3510 .DISPNUM% LDA HOUR%

3520 JSR L_NUM%

3530 JSR OSWRCH%

3540 LDA HOUR%

3550 JSR RNUM%

3560 JSR OSWRCH%

3570 LDA #58

3580 JSR OSWRCH%

3590 LDA MIN%

3600 JSR L_NUM%

3610 JSR OSWRCH%

3620 LDA MIN%

3630 JSR RNUM%

3640 JSR OSWRCH%

3650 LDA #58

3660 JSR OSWRCH%

3670 LDA SEC%

3680 JSR L_NUM%

3690 JSR OSWRCH%

3700 LDA SEC%

3710 JSR RNUM%

3720 JSR OSWRCH%

3730 RTS

3740 \

4000 .L_NUM% LSR A

4010 LSR A

4020 LSR A

4030 LSR A

4040 ORA #&30

4050 RTS

4060 \

4070 .RNUM% AND #&0F

4080 ORA #&30

4090 RTS

4100 \

4110 .RESET% LDX #(T_LEN% MOD 256)

4120 LDY #(T_LEN% DIV 256)

4130 LDA #4

4140 JSR OSWORD%

4150 RTS

4160 \

4200 .INIT% LDA #(TICK% MOD 256)

4210 STA EVNTV%

4220 LDA #(TICK% DIV 256)

4230 STA EVNTV%+1

4240 JSR RESET%

4250 LDA #14

4260 LDX #5

4270 JSR OSBYTE%

4280 RTS

5000 ]

5010 REM DATA

5020 HOUR%=FNequb(0)

5030 MIN%=FNequb(0)

5040 SEC%=FNequb(0)

5050 XTEMP%=FNequb(0)

5060 YTEMP%=FNequb(0)

5070 T_LEN%=FNequb(155)

5080 dummy=FNequw(&FFFF)

5090 dummy=FNequw(&FFFF)

5110 ENDPROC

9000 REM MACROS

9010 DEF FNequb(VA%)

9020 ?P%=(VA% MOD 256)

9025 IF PASS=3 THEN PRINT ~P%;"=";~?P%

9030 P%=P%+1

9040 =P%-1

9100 DEF FNequw(VA%)

9110 ?P%=(VA% MOD 256)

9115 IF PASS=3 THEN PRINT ~P%;"=";~?P%

9120 P%?1=(VA% DIV 256)

9125 IF PASS=3 THEN PRINT ~P%+1;"=";

~(P%?1)

9130 P%=P%+2

9140 =P%-2

9200 DEF FNequs(S$)

9210 $P%=S$

9220 IF PASS=3 THEN PRINT ~P%;"=";S$

9230 P%=P%+LEN(S$)+1

9240 =P%-LEN(S$)-1

9300 DEF PROCsave

9400 [OPT PASS

9410 PHA

9420 PHP

9430 TXA

9440 PHA

9450 TYA

9460 PHA

9470 ]

9480 ENDPROC

9500 DEF PROCrestore

9510 [OPT PASS

9520 PLA

9530 TAY

9540 PLA

9550 TAX

9560 PLP

9570 PLA

9580 ]

9590 ENDPROC

If you run the above program you will see the clock display appear at the top of the screen as soon as INIT% has been called. This display will remain until you press CTRL and BREAK, so forcing a full system restart or until you load another program over the machine code stored in CODE%.
Obviously to make the program of permanent use it has to be installed in the system in such a way that it cannot be overwritten by accident. The only real way of doing this is to increase the value of PAGE and store it in the free memory that this creates. To be precise:

(1) Save the above program on tape or disk and then perform a full system reset (either by switching the machine off and on or by pressing CTRL and BREAK).

(2) Enter

PRINT~PAGE

and note down the result.

(3) Now enter PAGE=PAGE+256 and once again PRINT„PAGE and note down the result. This reserves 256 bytes for the clock program.

(4) Now load the clock program and change line 20 to read PROCasm(x) where x is the original value of PAGE noted down in step 1. Also remove line 30 to stop the clock from being started.

(5) Now run the program and enter PRINT INIT% and note down the result. This gives the address of the start of the program.

(6) Finally using the information collected in steps (1), (3) and (5) enter:

*SAVE TIMER x y z

where x is the value of PAGE as obtained instep (1), y is the value of PAGE obtained in step (3) and z is the value of INIT% obtained in step (5). Following steps (1) to (5) all you have todoto run the timer is to remember to reserve the memory space that it needs using PAGE=PAGE+256 and then enter the command *RUN TIMER.
The background timer given above certainly works and does the job that was intended, but it suffers from a few shortcomings. The most obvious is that there is no way to set M to the current time! This is not a serious problem as it is not difficult to write a BASIC program that will store the values of the current time in the memory locations used tor HOUR%, MIN% and SEC%. A more serious defect is that the DISPLAY% routine affects the working of the COPY key by moving the copying cursor as well as the text cursor back to its 'original' position. This is typical of the sort of unexpected side effect of an interrupt or event driven program. In this case the only solution is to provide a subroutine that will print the time on the screen without using the MOS and this is a very large project. A much smaller criticism of the program is that it doesn't check to make sure that the event that has happened really is the interval timer reaching zero after all there might be other enabled events. As each type of event corresponds to a different event code stored in the A register, this is easy to remedy. The code for the event timer crossing zero is 5 so, adding:

2991 CMP #5
2992 BEQ TICK%
2993 RTS

will ensure that the clock is only updated by the correct event.
You can use the methods outlined in this project to use events in your own programs. In particular, you should now be able to see how to print the contents of a file while running other programs by using the 'print buffer empty' event to call a routine that will fill the print buffer. However, it is worth mentioning that programs that use interrupts or events can be very difficult to understand and very difficult to debug so proceed with care!


Next     Top