8-Bit Software

The BBC and Master Computer Public Domain Library

Back to 8BS

Return To On Line Magazine

Back to y2k fix page

Master 128 Y2K Repair From Mark Bush 10/10/2002 

CJR: The listing by Mark doesn't work on my Master.
However, Raf Giaccio has debugged and amended it to preserve PAGE by using filing system workspace RAM

BBC Master Y2K Fix

It is well known that the BBC Master series CMOS clock has a bit of a problem when it comes to the year 2000. A few possible solutions exist falling under three categories: The first of these solves nothing as any application which accesses the clock will still not get the right information. The second is a tricky solution requiring careful work on the motherboard by the user with potentially disasterous results if not done exactly right.

The third is a viable alternative, however buying and fitting a ROM for such a simple problem seems a little over-kill (although the chip available does claim to fix a couple of other things, too).

The solution provided here makes use of the sideways RAM these machines have to install ROM code. The program is provided free and doesn't take too long to type in. The only issue is that the generated ROM image needs reloading every time the machine is switched on, however if you use a !BOOT file to do this on a disc that is always in the drive when the machine is switched on, this will be loaded automatically.

Currently, this solution just changes the century from "19" to "20". The next version will enable the user to set which year to change on, support timezones and account for daylight saving time to enable the internal clock to be kept on UTC but the displayed time to be local time if the user so wishes.

The following first describes what the problem is, then explains the details of how the clock is read and then describes how the solution fits in to this mechanism. Finally, a complete listing of the program will be provided with details as to how it works.

The Problem

The CMOS clock on the BBC Master series computers does not provide information on the century. Thus, when the date is set, the century supplied is ignored and when it is read, the MOS just adds "19" to the year. This means that at the end of 1999, these computers progressed to 1900 remaining steadfastly in the 20th century.

How The Clock Is Read

When you access TIME$ or issue the *TIME command, the system uses OSWORD call &0E which access a MOS routine to read or write the bytes from the CMOS chip. The call is set up by setting the X and Y registers to point to the start of some free RAM (low byte of the address in X, high byte in Y). The first location is set to either 0, 1 or 2 depending on the required output (this value being overwritten by the first byte of the result in each case).

(X;Y) = 0

In this case, there should be 24 more bytes available and the full text string obtained by decoding the CMOS clock is filled in ending in a carriage return. This is the string shown when you print TIME$ or issue the *TIME command.

(X;Y) = 1

In this case, there should be 6 more bytes available and the CMOS chip contents are copied directly. The bytes are all in BCD format.

(X;Y) = 2

The final case expects the 7 following bytes to already contain the CMOS bytes (as generated by case 2 above) and turns them into the string representation produced by case 1 above, so there should 17 more bytes available to total the 25 required.


For example, suppose you wanted to put the string representation of the clock into zero page starting at &70. In assembly language you might do something like:
LDX #&70  \ set X and Y to point to &0070
LDY #&00
LDA #&00  \ store a zero in the first place
STA &70
LDA #&0E  \ perform OSWORD &0E
In BASIC, you can access TIME$ like a normal string variable or, to acheive the same result as the above assembly code, you can do:

What Is Happening?

When you make the machine code call to &FFF1, you access OSWORD via its usual entry point on all BBC micros. At that location is the instruction:
JMP (&020C)
which redirects the call to the place stored in locations (&020C;&020D) (known as WORDV) which in MOS 1.2 will probably be &EF39 - the location of the actual MOS OSWORD routine.

This routine checks the contents of the accumulator and performs whatever function is indicated. In our case, the bytes of the CMOS clock are read and the required output format written to the area specified by the X and Y registers. This routine specifically sets the century to "19".

How Can We Solve This?

The above has hinted at a possible solution. By changing the contents of the WORDV vector, we can direct OSWORD calls to a piece of code of our own choosing. This code can call the real OSWORD routine and, if the call is &0E, we can then alter the result by, say, placing "2" and "0" over the "1" and "9" before returning.

Unfortunately, the only "safe" place for our code is in a sideways RAM and we cannot guarantee that it will be paged in every time that OSWORD is called (indeed, we can guarantee that it will not!). Fortunately, the designers of the BBC micros realised this and provided what are known as extended vectors for all the routines that can be accessed by the redirection vectors in page 2. Access to these extended vectors is done by calling the MOS routines via the redirections which start at &FF00. There are 3 bytes here for each routine and they all contain:

We will come to why they are all the same shortly. OSWORD is always 7th in vector lists, so the relevant call is offset by 6 vectors which is 18 bytes, so it will be at &FF12 so to use extended vectors, we put &FF12 into (&020C;&020D).

The routine at &FF51 is the extended vector redirection routine. The reason why all the vecors go through this one place is that it extracts the offset of the address it came from (&12 in our case) and adds it to the base of the extended vector space to find the extended vector to route through. Normally this extended vector space is found starting at &0D9F, however OSBYTE &A8 returns the start address and so user programs using the area should get the address this way rather than relying on &0D9F.

Each of these extended vectors has 3 bytes. The first two are a standard vector in low byte, high byte format. The third byte should contain the number of a ROM that the system will page in before jumping to the vector. The previous ROM will be paged back in when the vector returns.

We can now see how to put our own code in place. We set (&020C;&020D) to &FF12 to access extended vectors and set (&0DB1;&0DB2) to point to the address of our new code in our ROM and put the ROM number into &0DB3 (though we will actually get the correct addresses for this from OSBYTE &A8, of course). We will also have to remember the previous contents of (&020C;&020D) so that we can jump there to use the real OSWORD routine.

The question now is how do we set this up? Every time you issue a Break (with or without shift or control) the MOS resets the contents of all the normal and extended vectors! Well, fortunately, every time this happens, each ROM, in turn, gets sent various signals and one of these is a reset signal (&27) to tell the ROM to do any initialisation required. Our ROM just needs to setup the vectors every time it gets this signal.

Since we need to remember the previous contents of (&020C;&020D) we also need to have some RAM for our workspace. We will receive a signal (&02) to our ROM enabling us to do this on every hard reset (control-break). Note that we could utilise the fact that we will run in RAM and so use locations in our address space for this purpose. The solution I have provided here does not do this so that, if the user really wanted, they would be able to burn a ROM with the generated code to install permanently and the solution will still work.

We now know that we need to set up a ROM image which responds to service call &02 to grab some workspace RAM and call &27 to set up the redirection vectors. All we need to do know is work out what we need to do to actually fix the date!

Intercepting OSWORD

Intercepting a system routine such as OSWORD comes with certain responsibilities. The main requirement is that for all calls that we are not interested in ourselves, we must ensure that the real OSWORD routine runs and returns to the calling program as if we had not intercepted it. If we are interested in the call, we should ensure that the results we return are exactly consistant with the normally expected results so that no programs break because of what we do.

Obviously, it is also vital that any use we make of the stack is completely cleaned up and that we preserve any registers or memory we utilise, though what we do in our workspace is, of course, our own affair!

Possibly the trickiest part of all this is actually calling the real OSWORD routine. We can't guarantee where it is so we read it from (&020C;&020D), store the result in our workspace and somehow use that address. One way of doing this would be to store a jump instruction somewhere and jumping there. This proves too tricky in practice as we cannot do this in our workspace as we don't know in advance where this will be and we cannot do it in general RAM as we would then violate the requirement that we always preserve memory we use.

The solution here is to utilise the way in which the RTS instruction works. This instruction takes the top two bytes off the stack, adds 1 and uses this as the location of the next instruction (the JSR adds 2 to the program counter before pushing it onto the stack, so normally this process results in control passing to the next instruction which is 3 bytes after the start of the JSR).

To call OSWORD, we push 1 less than the address of the real routine onto the stack and perform an RTS. This will take us to the OSWORD routine. We must ensure, however, that when OSWORD performs its own RTS that it has somewhere to return to. This means that if we have nothing more to do ourselves (ie. that we are not interested in this call) that we empty the stack of everything we have put on there leaving only the OSWORD-1 address. Below this on the stack will be the return address of the routine that first called OSWORD and all will be well.

If we want to further process the result of the OSWORD call (ie. for call &0E) we need to add two extra bytes to the stack before OSWORD-1 to specify an address for OSWORD to return to under our control.

These are very common tricks used in ROMs and worth understanding if you are doing any serious assembly programming.

Solution Listing

The full listing of the BASIC program to generate the ROM code is available here. Version 2 adapted by Raf Giaccio here

Here's the legal bit. You knew this was coming, didn't you? Sorry, but this is required these days. You are free to copy the program and use it or to extract out any parts that you think are useful for your own programs. I have tested this code extensively on a real BBC Master, however it is provided "as is" without any warranty, either expressed or implied, and I am not liable for any damage that may result in its use. Phew!

Type the program in and save it. When you run it, it will generate the ROM image in normal RAM and then save it as "Y2KROM". You can then load this into a sideways RAM bank using:

*SRLOAD "Y2KROM" 8000 7
This will load it into slot 7. Change this to load into the slot you wish to use. You will then need to perform a hard reset (using control-break) to activate the image. Check that it is working by running:
The listing is annotated to explain what is happening at each stage. Note the need to subtract 1 from both the OSWORD address and the RET address (which we save so that we don't have to keep calculating it each time the OSWORD routine is called. The subtraction of 0 (zero) from the high byte each time is to account for the possibility that we cross a page boundary (ie. the low byte is zero) in which case the carry flag will become clear and we will end up subtracting 1.

If you are able to transfer files to your BBC, then you can download a pre-assembled copy of the ROM image here.


 Back to 8BS
Return To On Line Magazine