Автор: Carol Brooksbank
Год: 1985
Издатели: ZX Computing
Языки:
Английский
Формат:
TZX лента
Требования:
ZX Spectrum 16K,
ZX Spectrum 48K
Ссылки:
Страница на ZXArt
Страница на World Of Spectrum
Страница на Spectrum Computing
Скриншоты:
Год: 1985
Издатели: ZX Computing
Языки:
Формат:
Требования:
Ссылки:
Скриншоты:
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 ]
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 ]