Clocking On Carol Brooksbank keeps track of time with this useful machine code program. When you are planning a game program with an 'against the clock' element, it would e useful to be able to display a clock running continuously on the screen. The spectrum has its own built in clock. The problem is how to display it, and keep it running, on a machine which is not equipped for multi-tasking. The Spectrum handbook has a BASIC program which, by PEEKing the three FRAMES system variables, and calculating the time elapsed from the values held there, produces an accurate clock. The trouble is that while the Spectrum is doing that it is not doing anything else. It seems that you can have the clock or your game, but not both. But it can be done, if you know a little about the interrupt modes. Interrupts In the normal state of Spectrum affairs, it stops what it is doing 50 times per second and performs the instructions in the ROM subroutine at 0038h - updating the FRAMES variables and scanning the keyboard. This is Interrupt Mode 1 (IM1). But there is another Interrupt Mode, IM2. (Actually there are two, but this is the one which concerns us here.) In IM2, you can direct the Spectrum, at an interrupt, to your own subroutine in the RAM, and give it other tasks to perform. It is a powerful instruction, but must be handled with great care, as there are a number of conditions which must be met if you want to avoid everything going horribly wrong. First, your interrupt subroutine must be short. There are 50 interrupts per second, so if your instructions take longer than 1/50 sec. to perform, the program will crash. The Spectrum will not return from the interrupt subroutine before it is called again, and it will do nothing but repeat the first part of the subroutine over and over. Although the Spectrum can perform around 3000 instructions in 1/50 sec., it is easy to overrun the time. You will still need to scan the keyboard, and the scanning routine itself is quite a long one. When planning this program I tried to use a machine code version of the handbook BASIC program, using the calculator stack for the calculations, and the ordinary RST10 printing routine for displaying the clock. It crashed. Each calculator stack instruction calls up various ROM subroutines, and the total number of instructions was far too large for the time available. So be careful of ROM subroutines. A one byte instruction on paper can call up a very complex network of instructions in practice. So, I simplified the subroutine, no longer updating FRAMES, but introducing out own clock counters, the program variables at FF01-FF09 (7F01-7F09 for 16K). This eliminated the calculator stack, but I was still using a series of RST10 instructions for the display, and, I found another snag. Using RST10 alters some of the system variables - TV FLAG, S POSN, etc. So although the subroutine could just be performed in the time, the main program went haywire because of the corrupted variables. When I tried to save all the variables involved at the beginning of the sub- routine, and restore them at the end, we were over time again. So out went RST10 in favour of direct POKEs into the screen for displaying the clock. It is essential to save all the registers at the start of an interrupt subroutine and restore them before return- ing from it to avoid their corruption causing the main program to crash. Even a BASIC program will crash if you neglect this. Although the clock routine does not appear to end with a series of POP instructions, they are there. It ends by jumping into the usual interrupt subroutine at the point where it scans the keyboard, and the POPs are the final instructions of that subroutine. The other major difficulty is the devious route the Spectrum takes to its interrupt subroutine when in IM2. On an interrupt, it jumps to an address whose low byte is FFh, and whose high byte it gets from the I register (not to be confused with the IX or IY registers). At this vector ad- dress, it expects to find the actual subroutine address. So before the IM2 instruction, you must load the I register with the high byte of the vector address, and the vector address must hold the two bytes of the subroutine address, in the usual format of low byte first. In this program, the 48K subroutine is at FEC5, and the vector address at FEFF, so the I register holds FE, and at FEFF and FF00 are the bytes 5CFE. When returning to IM1, the I register must be restored to its usual value of 3F. But, there is another complication. If the I register holds any value between 40h and 7Fh, nasty things happen to the screen display. You need someone who knows more about what goes on under the Spectrum's bonnet than I do to ex- plain why, but it is so. This does not matter to the 48K user, who can use 80FF or higher, but for 16K folk it means that there is nowhere in the RAM where you can put the vector address. So the 16K user wishing to use IM2 must rummage about in the ROM, looking for an address, whose low byte is FF, at which there are two bytes which point to an address in the 16K RAM. I came up with six. I am not pre- pared to swear there are no more, but these are enough to go with (see figure 1). Figure 1. Available addresses for the 16K user ROM ADDRESS (vector) Bytes There RAM ADDRESS 06FF DD71 71DD 0FFF 186D 6D18 14FF 6964 6469 19FF 225D 5D22 1EFF CD67 67CD 28FF 5C7E 7E5C Of these, some are so low in memory that they would leave very little room for BASIC. I stopped looking when I found the one at 28FF, which gives the RAM address 7E5C, because the subroutine fits neatly there without overwriting the user-defined graphics. Conversion To simplify the conversion between machines, I have placed the 48K routine at FE5C. The listing is for 48K, but I have underlined the bytes which must be changed for 16K, and included the 16K bytes in brackets. [Naturally, the TZX contains both versions of the code.] By a happy accident, the subroutine ends at FEFE, so that the vector address can follow immediately, between the subroutine and the program variables. If the 'FF' address falls in the middle of the subroutine, the vector address must beplaced there, with a jump instruction immediately preceding it so that the sub- routine operation can by-pass the subroutine address bytes. The start and stop routines do not have to reside in memory where I have placed them. If your main program is in machine code, the first bytes should be the start routine, and the final ones the stop routine. If you leave them where they are when your program is in BASIC, RANDOMIZE USR 65057 (32289) will start the clock and RANDOMIZE USR 65109 (32341) will return to the normal interrupt status. If you wish to pause the clock during your program, e.g. to allow for reading instructions without loss of time, simply stop it with RANDOMIZE USR 65109 (32341). RANDOMIZE USR 65057 (32289) will start it again from where it left off. To start it again from zero, yo must POKE 0 into the following variables: Spectrum 48K Spectrum 16K FF02 65282 7F02 32514 FF03 65283 7F03 32515 FF05 65285 7F05 32517 FF06 65286 7F06 32518 FF08 65288 7F08 32520 FF09 65289 7F09 32521 FF01 65281 7F01 32513 Do not overwrite the clock "window" with anything during your program or you will lose the words "TIME ELAPSED" [but see the demo program on the TZX for a way around this]. The clock irself will reappear, however, even after CLS. The window is AT: 0, 24-31 1, 24-32 2, 24-31 Enter CLEAR 65056 (32288) to protect the clock above RAMTOP. If you have just time for an hour's programming before the pub opens or you have to feed the cat, you can run the clock and keep an eye on the time while you work. Once the code is loaded, RANDOMIZE USR 65057 (32289) will start the clock, and you can use the Spectrum quite nor- mally. NEW will delete "TIME ELAPSED", but so long as you remembered the CLEAR 65056 (32288) instruction, the clock will continue. SAVE, LOAD, VERIFY and using the printer will pause it briefly, and a scroll will remove "TIME ELAPSED" and give you a copy of the time the scroll oc- curred above the running clock, but otherwise it will run accurately for 99 hrs., 59 min., 59 sec., after which the printing will be corrupted. (Anyone who wants to spend more than four days and nights at the keyboard can write their own routine!) The following BASIC will allow you to insert any com- mands you need for your own program. 10 RANDOMIZE USR 65057 (32289) 9899 GO TO 9999 9900 CLEAR 65056 (32288) 9910 LOAD ""CODE 9999 RANDOMIZE USR 65109 (32341) Save the program on tape /before/ running the clock, so that it always starts from zero when loaded. SAVE "program" LINE 9900 SAVE "clock"CODE 65057 (32289),235 The clock runs in real time, but you can change this by POKEing other values into FE69, 65129 (7E69, 32361). Values lower than 32h, 50d, will speed the clock up, higher values will slow it, enabling you to change the level of difficul- ty in your program. If you wish to make some operation in your program conditional on a certain time having passed, you can PEEK the variables. For example, PEEKing M10, FF05, 65285 (7F05, 32517) will tell you that 10 minutes have passed each time the value held there changes. Sources When devising the program, I made extensive use of the following books: LOGAN Understanding your Spectrum (Technical explanation of interrupt instructions.) [Your humble typist seconds this recommendation.] LOGAN & O'HARA The complete Spectrum ROM disassembly (Details of the purpose and structure of all ROM subroutines.) (Both above published by Melbourne House.) ROSS-LANGLEY The Spectrum machine code reference guide (Full listing of all ROM addresses and the bytes held there.) (Published Interface.) [The following note accompanied the assembly code listing of the routine.] ** With the exception of FF04 and FF07, the locations FF01 - FF0B are the variables which will be altered as the program proceeds. The CODE of the digits is 30h - 39h, and the CODE of ":" is 3Ah. As the value stored in the variable for each digit is not the CODE, but the true value, the value stored is (CODE-30h). To store ":" in the same con- vention, it is therefore necessary to store 0A wherever it is required to print ".". [ And finally, a word about the TZX that goes with this text. It should be apparent that I've provided loadable CODE files for both 48k and 16k Spectrums. I've also added the programs I used to load those into memory, should those be useful to someone. The TZX starts with a demonstration program, which deter- mines whether it is running on a 16k or 48k Spectrum (unless you fiddle with RAMTOP before loading it, in which case, serves you right), loads the appropriate CODE file, sets a few address variables, and proceeds to show how to use the routine. Note how, apart from the LOADing and address setting, it was possible to keep the code identical for both types of Spectrum. RLB, 01/2012 ]