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.
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".
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:
JSR &FF51
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!
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.
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:
*TIME
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.