126 lines
No EOL
7 KiB
Markdown
126 lines
No EOL
7 KiB
Markdown
#### The Zen Timer Is a Means, Not an End {#Heading5}
|
||
|
||
We're going to spend the rest of this chapter seeing what the Zen timer
|
||
can do, examining how it works, and learning how to use it. I'll be
|
||
using the Zen timer again and again over the course of this book, so
|
||
it's essential that you learn what the Zen timer can do and how to use
|
||
it. On the other hand, it is by no means essential that you understand
|
||
exactly how the Zen timer works. (Interesting, yes; essential, no.)
|
||
|
||
In other words, the Zen timer isn't really part of the knowledge we
|
||
seek; rather, it's one tool with which we'll acquire that knowledge.
|
||
Consequently, you shouldn't worry if you don't fully grasp the inner
|
||
workings of the Zen timer. Instead, focus on learning how to *use* it,
|
||
and you'll be on the right road.
|
||
|
||
#### Starting the Zen Timer {#Heading6}
|
||
|
||
**ZTimerOn** is called at the start of a segment of code to be timed.
|
||
**ZTimerOn** saves the context of the calling code, disables interrupts,
|
||
sets timer 0 of the 8253 to mode 2 (divide-by-N mode), sets the initial
|
||
timer count to 0, restores the context of the calling code, and returns.
|
||
(I'd like to note that while Intel's documentation for the 8253 seems to
|
||
indicate that a timer won't reset to 0 until it finishes counting down,
|
||
in actual practice, timers seem to reset to 0 as soon as they're
|
||
loaded.)
|
||
|
||
Two aspects of **ZTimerOn** are worth discussing further. One point of
|
||
interest is that **ZTimerOn** disables interrupts. (**ZTimerOff** later
|
||
restores interrupts to the state they were in when **ZTimerOn** was
|
||
called.) Were interrupts not disabled by **ZTimerOn**, keyboard, mouse,
|
||
timer, and other interrupts could occur during the timing interval, and
|
||
the time required to service those interrupts would incorrectly and
|
||
erratically appear to be part of the execution time of the code being
|
||
measured. As a result, code timed with the Zen timer should not expect
|
||
any hardware interrupts to occur during the interval between any call to
|
||
**ZTimerOn** and the corresponding call to **ZTimerOff**, and should not
|
||
enable interrupts during that time.
|
||
|
||
### Time and the PC {#Heading7}
|
||
|
||
A second interesting point about **ZTimerOn** is that it may introduce
|
||
some small inaccuracy into the system clock time whenever it is called.
|
||
To understand why this is so, we need to examine the way in which both
|
||
the 8253 and the PC's system clock (which keeps the current time) work.
|
||
|
||
The 8253 actually contains three timers, as shown in Figure 3.1. All
|
||
three timers are driven by the system board's 14.31818 MHz crystal,
|
||
divided by 12 to yield a 1.19318 MHz clock to the timers, so the timers
|
||
count once every 838.1 ns. Each of the three timers counts down in a
|
||
programmable way, generating a signal on its output pin when it counts
|
||
down to 0. Each timer is capable of being halted at any time via a 0
|
||
level on its gate input; when a timer's gate input is 1, that timer
|
||
counts constantly. All in all, the 8253's timers are inherently very
|
||
flexible timing devices; unfortunately, much of that flexibility depends
|
||
on how the timers are connected to external circuitry, and in the PC the
|
||
timers are connected with specific purposes in mind.
|
||
|
||
Timer 2 drives the speaker, although it can be used for other timing
|
||
purposes when the speaker is not in use. As shown in Figure 3.1, timer 2
|
||
is the only timer with a programmable gate input in the PC; that is,
|
||
timer 2 is the only timer that can be started and stopped under program
|
||
control in the manner specified by Intel. On the other hand, the
|
||
*output* of timer 2 is connected to nothing other than the speaker. In
|
||
particular, timer 2 cannot generate an interrupt to get the 8088's
|
||
attention.
|
||
|
||
Timer 1 is dedicated to providing dynamic RAM refresh, and should not be
|
||
tampered with lest system crashes result.
|
||
|
||
\
|
||
**Figure 3.1** *The configuration of the 8253 timer chip in the PC.*
|
||
|
||
Finally, timer 0 is used to drive the system clock. As programmed by the
|
||
BIOS at power-up, every 65,536 (64K) counts, or 54.925 milliseconds,
|
||
timer 0 generates a rising edge on its output line. (A millisecond is
|
||
one-thousandth of a second, and is abbreviated ms.) This line is
|
||
connected to the hardware interrupt 0 (IRQ0) line on the system board,
|
||
so every 54.925 ms, timer 0 causes hardware interrupt 0 to occur.
|
||
|
||
The interrupt vector for IRQ0 is set by the BIOS at power-up time to
|
||
point to a BIOS routine, **TIMER\_INT,** that maintains a time-of-day
|
||
count. **TIMER\_INT** keeps a 16-bit count of IRQ0 interrupts in the
|
||
BIOS data area at address 0000:046C (all addresses in this book are
|
||
given in segment:offset hexadecimal pairs); this count turns over once
|
||
an hour (less a few microseconds), and when it does, **TIMER\_INT**
|
||
updates a 16-bit hour count at address 0000:046E in the BIOS data area.
|
||
This count is the basis for the current time and date that DOS supports
|
||
via functions 2AH (2A hexadecimal) through 2DH and by way of the DATE
|
||
and TIME commands.
|
||
|
||
Each timer channel of the 8253 can operate in any of six modes. Timer 0
|
||
normally operates in mode 3: *square wave mode*. In square wave mode,
|
||
the initial count is counted down two at a time; when the count reaches
|
||
zero, the output state is changed. The initial count is again counted
|
||
down two at a time, and the output state is toggled back when the count
|
||
reaches zero. The result is a square wave that changes state more slowly
|
||
than the input clock by a factor of the initial count. In its normal
|
||
mode of operation, timer 0 generates an output pulse that is low for
|
||
about 27.5 ms and high for about 27.5 ms; this pulse is sent to the 8259
|
||
interrupt controller, and its rising edge generates a timer interrupt
|
||
once every 54.925 ms.
|
||
|
||
Square wave mode is not very useful for precision timing because it
|
||
counts down by two twice per timer interrupt, thereby rendering exact
|
||
timings impossible. Fortunately, the 8253 offers another timer mode,
|
||
mode 2 (divide-by-N mode), which is both a good substitute for square
|
||
wave mode and a perfect mode for precision timing.
|
||
|
||
Divide-by-N mode counts down by one from the initial count. When the
|
||
count reaches zero, the timer turns over and starts counting down again
|
||
without stopping, and a pulse is generated for a single clock period.
|
||
While the pulse is not held for nearly as long as in square wave mode,
|
||
it doesn't matter, since the 8259 interrupt controller is configured in
|
||
the PC to be edgeand hence cares only about the existence of a pulse
|
||
from timer 0, not the duration of the pulse. As a result, timer 0
|
||
continues to generate timer interrupts in divide-by-N mode, and the
|
||
system clock continues to maintain good time.
|
||
|
||
Why not use timer 2 instead of timer 0 for precision timing? After all,
|
||
timer 2 has a programmable gate input and isn't used for anything but
|
||
sound generation. The problem with timer 2 is that its output can't
|
||
generate an interrupt; in fact, timer 2 can't do anything but drive the
|
||
speaker. We need the interrupt generated by the output of timer 0 to
|
||
tell us when the count has overflowed, and we will see shortly that the
|
||
timer interrupt also makes it possible to time much longer periods than
|
||
the Zen timer shown in Listing 3.1 supports. |