-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathHTIMER.CPP
384 lines (320 loc) · 14.9 KB
/
HTIMER.CPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
//-------+---------+---------+---------+---------+---------+---------+---------+
// 1991-1992 Betz Associates. Released to the Public Domain. This source code
// is offered without warranty or guarantee of any kind, and the author(s)
// expressly disclaim any responsibility for consequences of it's use in
// products for commercial, shareware, or public domain release of any kind.
//
// This release of Copyright applies only to this source code module, and
// does not affect other source code modules which may be included with it
// in a program.
//
// File Name: HTIMER.CPP
// Project: Flights of Fantasy
// Creation: August 2, 1992
// Author: Mark Betz (MB)
//
// Machine: IBM PC and Compatibles
//
// Includes: htimer.h, types.h, dos.h
//
//-------+---------+---------+---------+---------+---------+---------+---------+
// Change History
// ------ -------
//
// Date Rev. Author Purpose
// ---- ---- ------ -------
// 8-2-1992 1.0 MB Initial release
// 9-20-1992 1.1 MB -corrected several potential hdwr
// problems.
// -added getElapsed() member func.
// -added setCount() member func.
// -see HTIMER.DOC for change list
//
// Description
// -----------
// Class member definitions, support functions, and control variables
// for the hi-res timer class. The HTimer implements a microsecond
// timer. Multiple timers may be instanced. The first timer created
// initializes the 8254 timer channel 1. The last timer resets it to
// it's default mode.
//
// NOTE: it is strongly recommended that you read the accompanying
// HTIMER.DOC file before using this class.
//-------+---------+---------+---------+---------+---------+---------+---------+
#include <dos.h>
#include <stdio.h>
#ifndef __HTIMER__
#include "htimer.h"
#endif
// initialization of static member data
unsigned int HTimer::numTimers = 0; // instance counter
unsigned int HTimer::countVal = 0; // 8254 counter value
unsigned long HTimer::ticks = 0; // interrupt 8 tick counter
// 8254 timer control commands
static const int timerMode = 0x043; // 8254 mode register port
static const int timer0Data = 0x040; // counter port for channel 0
static const unsigned char latchCmd = 0; // 0 Byte for latch command
static const unsigned char mode3set = 0x36; // 8254 mode set commands
static const unsigned char mode2set = 0x34;
static unsigned long longVal = 4294967295; // maximum value of unsigned long
// pointer to low unsigned int of bios time of day counter at 0040:006C
static volatile unsigned int far*bios_ticks=(unsigned int far*)MK_FP(0x40,0x6c);
static void interrupt (*Old08Handler)(...); // pointer to old timer isr
// The following two functions program the 8254 timer chip to operate in
// either pulse mode (2), or square wave mode (3). Square wave is the bios
// default mode of timer operation. In both modes the value for the channel
// 1 counter is 65536 (actually 0 .. 65535). The difference between the two
// modes lies in the manipulation of the counter. In square wave mode, each
// pulse of the 1.193180 Mhz input signal decrements the value by 2. At
// terminal count (0) the CLKOUT1 line is lowered and the counter is reloaded.
// On the next terminal count CLKOUT1 is raised, and the timer interrupt
// is generated on IRQ 0. The state of CLKOUT1 plotted against time results
// in a square wave. In pulse mode each pulse of the input signal decrements
// the counter value by 1, and CLKOUT1 starts high. At terminal count the
// counter is reloaded, CLKOUT1 goes low for one clock pulse, and is then
// raised again, and the timer interrupt on IRQ 0 is generated. Both modes
// generate the timer interrupt at approx. 18.2 hz, but mode 2 is more
// accurate for this purpose, since mode 3 timings must be divided by two
// to get an accurate count of elapsed microseconds.
// program 8254 timer chip channel 0 to operate in pulse mode
void SetTimerMode2( unsigned int count )
{
outportb(timerMode, mode2set); // send the mode 2 set command
__emit__(0x90, 0x90); // to the 8254, then dwell
// send the lsb of the count
outportb(timer0Data, *((unsigned char*)&count));
__emit__(0x90, 0x90);
// send the msb of the count
outportb(timer0Data, *(((unsigned char*)&count)+1));
return;
}
// reprogram the timer chip to operate in the bios default square wave mode
void SetTimerMode3( unsigned int count )
{
outportb(timerMode, mode3set); // send the mode 3 set command
__emit__(0x90, 0x90); // to the 8254
// send the lsb of the count
outportb(timer0Data, *((unsigned char*)&count));
__emit__(0x90, 0x90);
// send the msb of the count
outportb(timer0Data, *(((unsigned char*)&count)+1));
return;
}
// Constructor for HTimer class. On entry the function checks the numTimers
// counter to see if it is the only existing instance. If so, it programs
// the timer chip to mode 2 (pulse mode).
//
// NOTE: the numTimers counter is eight bits, and no test is performed on
// it's current value. Thus creating more than 255 timers will roll the
// counter over and cause the timer chip to be reset by the next timer
// object to go out of scope, while other timers are still using it.
//
// NOTE: the first instance of an HTimer object always reprograms the
// 8254 channel 0 timer to mode 2 operation using the default counter
// value of 0, which generates irq 0 interrupts at 18.2 hz. If you
// wish to reprogram the timer to run faster or slower you must do so
// AFTER instancing the first HTimer object, and you must use the
// HTimer::setCount() member function to do the reprogramming.
HTimer::HTimer()
{
if ( numTimers == 0 ) // is this the first instance?
{
SetTimerMode2( 0 ); // yes, program pulse mode
Old08Handler = getvect(0x8); // save current irq 0 isr
setvect(0x8, int8Handler); // hook irq 0 vector for int 8
}
numTimers++; // increment instances counter
tmrOn = 0; // reset timer on status
}
// Destructor for HTimer class. On entry the function checks the numTimers
// counter to see if it is the last existing timer. If so, it reprograms the
// timer channel to mode 3 (square wave), the bios default mode for channel
// 0
HTimer::~HTimer()
{
if ( numTimers == 1 )
{
SetTimerMode3( countVal );
setvect(0x8, Old08Handler);
}
numTimers--;
}
// **************************************************************************
// >>>>>> ISR: hardware interrupt IRQ 0 <<<<<<
// this static member function is an interrupt service routine for the timer
// hardware interrupt. It increments the static member tick counter on
// every interrupt, and wraps it at the max value of a long. The current
// number of ticks is used in calculating the elapsed microsecond count in
// HTimer::timerOff(), and HTimer::getElapsed()
void interrupt HTimer::int8Handler(...)
{
ticks++;
Old08Handler(); // chain the previous handler
}
// **************************************************************************
// this function is used by timerOff() and getElapsed() to calculate the
// elapsed time in microseconds
unsigned long HTimer::calcElapsed()
{
unsigned long elapsedTime = 0;// the value that will be returned
unsigned int pulsesPerTick; // number of clock pulses per interrupt
unsigned long endTick; // number of int 8 ticks at end of current run
unsigned long numTicks; // number of int 8 ticks elapsed during run
unsigned int finish8254; // finishing counter value
if ( tmrOn )
{
// disable interrupts and latch current 8254 channel1 counter value
asm cli;
endTick = ticks;
outportb(timerMode, latchCmd);
__emit__(0x90, 0x90);
*(unsigned char *)&finish8254 = inportb(timer0Data);
__emit__(0x90, 0x90);
*(((unsigned char *)&finish8254) + 1) = inportb(timer0Data);
asm sti;
// handle the case of a 0 counter value yielding 65535 pulses per
// tick (see 8254 programmable timer documentation for details).
if (countVal == 0)
pulsesPerTick = 65535;
else
pulsesPerTick = countVal;
// calculate number of IRQ 0 interrupts since timing started
if (endTick >= startTick)
numTicks = ticks - startTick;
else
numTicks = (longVal - startTick) + ticks;
// calculate elapsed counter value since timing started
if (numTicks == 0)
elapsedTime = (start8254 - finish8254);
else if (numTicks == 1)
{
elapsedTime = start8254;
elapsedTime += (pulsesPerTick - finish8254);
}
else
{
elapsedTime = ((numTicks - 1) * pulsesPerTick);
elapsedTime += start8254;
elapsedTime += (pulsesPerTick - finish8254);
}
// convert to microseconds, avoid overflowing elapsedTime
if (elapsedTime <= 4200000L) // for timing runs longer than 4
{ // seconds we lose precision
elapsedTime *= 1000; // this order of operations is more
elapsedTime /= 1193; // accurate because the fractional
} // part is maintained
else
{
elapsedTime /= 1193; // this order of operations is
elapsedTime *= 1000; // less accurate because the frac-
} // tional part is lost in the
} // division
return(elapsedTime);
}
// timerOn() starts the timing process by grabbing the current countdown
// value from the 8254 timer chip, channel 0. This count is decremented
// 1,193,180 times per second by the 1.193 Mhz input signal to the chip.
void HTimer::timerOn()
{
startTick = ticks; // set tick counter
tmrOn = 1; // set timer on flag
outportb(timerMode, latchCmd); // tell 8254 to latch count
__emit__(0x90, 0x90); // let the bus settle
// get the LSB of the count
*(unsigned char *)&start8254 = inportb(timer0Data);
__emit__(0x90, 0x90);
// get the MSB
*(((unsigned char *)&start8254) + 1) = inportb(timer0Data);
}
// timerOff() closes down the current timing process by grabbing the
// counter value from channel 0, then calculating the elapsed time in
// units of a microsecond.
unsigned long HTimer::timerOff()
{
unsigned long elapsedTime;
elapsedTime = calcElapsed(); // calculate elapsed time
startTick = 0; // reset timer parameters
start8254 = 0;
tmrOn = 0; // reset timer on flag
return(elapsedTime);
}
// this function returns the elapsed time for the current timing process in
// microseconds. The timer continues to run after the function returns
unsigned long HTimer::getElapsed()
{
return(calcElapsed()); // calc and return elapsed time
}
// The HTimer::setCount() function is used to reprogram the channel 1 timer
// countdown value. Note that this function reprograms the timer chip and
// effects ALL instances of HTimer operating at the time of the call to it.
// See the detailed explanation of the counter operation in HTIMER.DOC
void HTimer::setCount( unsigned int regCount )
{
SetTimerMode2( regCount );
countVal = regCount;
}
// **************************************************************************
// a test frame for the timer object; these three functions time the number
// of microseconds in one tick of the bios time counter. Since this counter
// "ticks" at a frequency of approx. 18.2 hz, we should expect 54,945.05
// microseconds per tick. The first times the interval by turning the timer
// on, then off. The second performs the same test using the polling function.
// The timer continues to run. The third sets the timer to interrupt at a
// custom rate, and then times a bios tick to make sure that the higher
// hardware interrupt rate does not effect the timing.
// tests elapsed time by turning timer on, then off
unsigned long testTimerOnOff()
{
HTimer timer1; // timer object for test
unsigned long etime;
unsigned int lastTick;
lastTick = *bios_ticks; // grab current time tick
while( lastTick == *bios_ticks ); // wait for it to change
lastTick = *bios_ticks; // it changed, grab the new value
timer1.timerOn(); // turn on the timer
while( lastTick == *bios_ticks ); // wait for it to change
etime = timer1.timerOff(); // turn off the timer
// value should be about 54,945
return(etime);
}
// tests elapsed time by turning timer off, then calling HTimer::getElapsed.
unsigned long testGetElapsed()
{
HTimer timer1; // timer object for test
unsigned long etime;
unsigned int lastTick;
lastTick = *bios_ticks; // grab current time tick
while( lastTick == *bios_ticks ); // wait for it to change
lastTick = *bios_ticks; // it changed, grab the new value
timer1.timerOn(); // turn on the timer
while( lastTick == *bios_ticks ); // wait for it to change
etime = timer1.getElapsed(); // turn off the timer
// value should be about 54,945
timer1.timerOff();
return(etime);
}
// this function first sets the channel 1 counter value to intRate, which
// changes the frequency at which interrupts are generated by the time on
// IRQ 0. It then times one bios time tick exactly as the previous two
// functions do. If caller of this function has set up an isr for interrupt
// 8, and is calling the original system isr for this interrupt at the
// proper frequency, we will expect to get the same value as with the other
// two test functions. Otherwise, this function will return the actual time
// between calls to the original interrupt handler.
unsigned long testFastCount( unsigned int intRate )
{
HTimer timer1; // timer object for test
unsigned long etime;
unsigned int lastTick;
timer1.setCount( intRate ); // alter interrupt rate
lastTick = *bios_ticks; // grab current time tick
while( lastTick == *bios_ticks ); // wait for it to change
lastTick = *bios_ticks; // it changed, grab the new value
timer1.timerOn(); // turn on the timer
while( lastTick == *bios_ticks ); // wait for it to change
etime = timer1.getElapsed(); // turn off the timer
// value should be about 54,945
timer1.setCount( 0 ); // reset int rate to default
return(etime);
}
// **************************************************************************