I was always looking for a way to control the timing of a program more
exactly than it is possible with TIMER (e.g. for animations, music, etc).
MTIMER did not provide any solution because you can call this function only
once and must then reset it (which includes, as I found, waiting for the
next "regular" timer tick). Finally I decided to write my own Interrupt 8
handler in order to control the frequency of the timer tick (see code below).
It works quite fine so far, but I still have some questions concerning
running these routines under Windows 95/98:
- In my experience it was not possible to set the timer to a higher frequency
than about 1000 Hz. When setting it to any value between 1000 and 2000 Hz,
the real frequency remained at 1000 Hz. Above 2000 Hz, a message box appears:
"This program probably does not run well under Windows." (CPU speed cannot
be the reason; the speed of my system is 266 MHz),
- I tried the same code WITHOUT calling the original INT 8 handler. It would
be expected that this would freeze the system time at the point when the new
interrupt handler was installed. Under DOS, it actually does, but under
Windows it did not affect the system time at all (although it affected the
value of TIMER in PowerBASIC).
- In conclusion: Could anybody explain me what exactly Windows is doing with
the timer, and if this could result in unexpected problems using the code
posted below? ...and maybe somebody has a hint how to increase the clock
frequency to more than 1000 Hz (under Windows)?
Hans Ruegg.
Code for Timer routines:
========================
DEFINT A-Z
DECLARE SUB setpithandler (count??, pithandler??())
DECLARE FUNCTION getpit() AS DWORD
DECLARE FUNCTION pitcheck() AS WORD
DECLARE SUB pitfreq (freq)
DECLARE SUB resetpithandler ()
DIM tmr??(28) 'array which holds code for the new INT 8 handler
CLS
setpithandler (&H1234DD\1000), tmr??() 'set clock frequency to 1000Hz
PRINT "New handler installed."
DO:
LOCATE 3,1: PRINT USING "##########"; getpit;
LOCATE 3,40: PRINT USING "#####.##"; TIMER;
LOOP WHILE INKEY$=""
resetpithandler
LOCATE 24,1: PRINT "Handler successfully restored."
END
'========================================================================
'Following routines are only auxiliary routines
'for setting up the INT 8 handler
SUB loadASM (matrix??(), hexcode$)
'used to convert a string containing hexadecimal code into real code
length?? = LEN(hexcode$)
posic?? = VARPTR(matrix??(0))
DEF SEG = VARSEG(matrix??(0))
FOR i?? = 1 TO length??-1 STEP 2
d? = VAL("&H" + MID$(hexcode$, i??, 2))
POKE posic??, d?
INCR posic??
NEXT i??
DEF SEG
END SUB
SUB putintvector (intno%, start??)
'Lets an interrupt vector point to an array element specified by "start??"
DEF SEG = 0
POKEI 4*intno%, VARPTR(start??)
POKEI 4*intno%+2, VARSEG(start??)
DEF SEG
END SUB
SUB putintaddress (intno%, segm??, offs??)
'Establishes a segment and offset address as an interrupt vector
DEF SEG = 0
POKEI 4*intno%, offs??
POKEI 4*intno%+2, segm??
DEF SEG
END SUB
SUB getintvector (intno%, segm??, offs??)
'Returns segment and offset of an interrupt vector
DEF SEG = 0
segm?? = peeki(4*intno%+2)
offs?? = peeki(4*intno%)
DEF SEG
END SUB
'=========================================================================
'Now following the proper timer routines:
SUB setpithandler (count??, pithandler??()) PUBLIC
'Substitutes INT 8 handler
'pithandler??(28) holds code
'count?? = new counter value (0 to 65535)
'Output: pithandler??(0) = LSB, pithandler??(1) = MSB of new tick count
' pithandler??(6) = segment, pithandler??(7) = offset of BIOS handler
' pithandler??(2) = count??
cod$ = "00000000000000000000000000000000"
cod$ = cod$ + "50FA2E83060000012E83160200002EA104002E01060A007302"
cod$ = cod$ + "CDF6FBCDF5B020E62058CFCF9050495448"
loadASM pithandler??(), cod$ '(Source code see below)
getintvector 8, sg??, offs??
pithandler??(6) = sg??: pithandler??(7) = offs?? 'load variables
pithandler??(2) = count??
! CLI
putintaddress &HF6, sg??, offs?? 'Relocate original INT 8 handler
'to INT F6
putintvector &HF5, pithandler??(26) 'points to IRET instruction
'(INT F5 will be available to hook
'a routine which needs to be called
'at each tick)
putintvector 8, pithandler??(8) 'Let INT 8 vector point to the entry
'point of the new handler
OUT &H43, &H34 'Set clock frequency
OUT &H40, count?? MOD 256
OUT &H40, count??\256
! STI
END SUB
FUNCTION getpit() PUBLIC AS DWORD
'returns tick count since "setpithandler" was initialized.
DEF SEG = 0
tickptr?? = PEEKI(32)
tickseg?? = PEEKI(34)
DEF SEG = tickseg??
getpit = PEEKL (tickptr??-16)
DEF SEG
END FUNCTION
FUNCTION pitcheck() PUBLIC AS WORD
'Installation check for pithandler
'Output: 1 = pithandler not installed,
' other values: = counter constant in pithandler
DEF SEG = 0
s?? = PEEKI(34): o?? = PEEKI(32)
DEF SEG = s??
Id$ = PEEK$ (o??+38, 4)
IF Id$="PITH" THEN pitcheck = PEEKI (o??-12) ELSE pitcheck = 1
DEF SEG
END FUNCTION
SUB pitfreq (freq) PUBLIC
'Changes timer frequency; freq = frequency in Hz
IF freq<19 OR freq>2000 THEN EXIT SUB 'because Windows does not accept
'higher frequencies
counter?? = &H1234DD\freq
DEF SEG = 0
s?? = PEEKI(34): o?? = PEEKI(32)
DEF SEG = s??
POKEI o??-12, counter?? 'Set counter constant in interrupt handler
DEF SEG
! CLI
OUT &H43, &H34 'Set new clock frequency
OUT &H40, counter?? MOD 256
OUT &H40, counter??\256
! STI
END SUB
SUB resetpithandler () PUBLIC
'retrieves original handler, resets original frequency.
! CLI
getintvector &HF6, sg??, offs??
putintaddress 8, sg??, offs??
OUT &H43, &H34 'Restore original clock frequency
OUT &H40, 0
OUT &H40, 0
! STI
END SUB
==============================
Source code for INT 8 handler:
==============================
Byte Machine code ASM
00 00 00 ;var: Tick count (low)
02 00 00 ;var: Tick count (high)
04 00 00 ;var: counter constant
06 00 00 ;var: real clockticks (low)
08 00 00 ;var: real clockticks (high)
0A 00 00 ;var: sum for real tick count
0C 00 00 ;var: BIOS Handler Segment
0E 00 00 ;var: BIOS Handler Offset
;Byte 10h = Routine Entry point
10 50 PUSH AX
11 FA CLI
12 2E CS: ; update Tick count
13 83 06 00 00 01 ADD WORD [0000], 1
18 2E CS:
19 83 16 02 00 00 ADC WORD [0002], 0
1E 2E CS: ; update real tick counter
1F A1 04 00 MOV AX, [0004]
22 2E CS:
23 01 06 0A 00 ADD WORD [000A], AX
27 73 02 JNC 002B ; overflow?
29 CD F6 INT F6 ; yes, then call original INT 8 ; handler (relocated at INT F6)
2B FB STI
2C CD F5 INT F5 ; INT F5 can be used to hook further
; routines
2E B0 20 MOV AL, 20 ; acknowledge interrupt
30 E6 20 OUT 20, AL ; (may not be necessary ?)
32 58 POP AX
33 CF IRET
34 CF IRET ; repeated at an EVEN byte position
; so the INT F5 vector can point to
; this instruction
35 90 NOP
36 50 49 54 48 ;Signature "PITH" for installation check
(58 bytes)
exactly than it is possible with TIMER (e.g. for animations, music, etc).
MTIMER did not provide any solution because you can call this function only
once and must then reset it (which includes, as I found, waiting for the
next "regular" timer tick). Finally I decided to write my own Interrupt 8
handler in order to control the frequency of the timer tick (see code below).
It works quite fine so far, but I still have some questions concerning
running these routines under Windows 95/98:
- In my experience it was not possible to set the timer to a higher frequency
than about 1000 Hz. When setting it to any value between 1000 and 2000 Hz,
the real frequency remained at 1000 Hz. Above 2000 Hz, a message box appears:
"This program probably does not run well under Windows." (CPU speed cannot
be the reason; the speed of my system is 266 MHz),
- I tried the same code WITHOUT calling the original INT 8 handler. It would
be expected that this would freeze the system time at the point when the new
interrupt handler was installed. Under DOS, it actually does, but under
Windows it did not affect the system time at all (although it affected the
value of TIMER in PowerBASIC).
- In conclusion: Could anybody explain me what exactly Windows is doing with
the timer, and if this could result in unexpected problems using the code
posted below? ...and maybe somebody has a hint how to increase the clock
frequency to more than 1000 Hz (under Windows)?
Hans Ruegg.
Code for Timer routines:
========================
DEFINT A-Z
DECLARE SUB setpithandler (count??, pithandler??())
DECLARE FUNCTION getpit() AS DWORD
DECLARE FUNCTION pitcheck() AS WORD
DECLARE SUB pitfreq (freq)
DECLARE SUB resetpithandler ()
DIM tmr??(28) 'array which holds code for the new INT 8 handler
CLS
setpithandler (&H1234DD\1000), tmr??() 'set clock frequency to 1000Hz
PRINT "New handler installed."
DO:
LOCATE 3,1: PRINT USING "##########"; getpit;
LOCATE 3,40: PRINT USING "#####.##"; TIMER;
LOOP WHILE INKEY$=""
resetpithandler
LOCATE 24,1: PRINT "Handler successfully restored."
END
'========================================================================
'Following routines are only auxiliary routines
'for setting up the INT 8 handler
SUB loadASM (matrix??(), hexcode$)
'used to convert a string containing hexadecimal code into real code
length?? = LEN(hexcode$)
posic?? = VARPTR(matrix??(0))
DEF SEG = VARSEG(matrix??(0))
FOR i?? = 1 TO length??-1 STEP 2
d? = VAL("&H" + MID$(hexcode$, i??, 2))
POKE posic??, d?
INCR posic??
NEXT i??
DEF SEG
END SUB
SUB putintvector (intno%, start??)
'Lets an interrupt vector point to an array element specified by "start??"
DEF SEG = 0
POKEI 4*intno%, VARPTR(start??)
POKEI 4*intno%+2, VARSEG(start??)
DEF SEG
END SUB
SUB putintaddress (intno%, segm??, offs??)
'Establishes a segment and offset address as an interrupt vector
DEF SEG = 0
POKEI 4*intno%, offs??
POKEI 4*intno%+2, segm??
DEF SEG
END SUB
SUB getintvector (intno%, segm??, offs??)
'Returns segment and offset of an interrupt vector
DEF SEG = 0
segm?? = peeki(4*intno%+2)
offs?? = peeki(4*intno%)
DEF SEG
END SUB
'=========================================================================
'Now following the proper timer routines:
SUB setpithandler (count??, pithandler??()) PUBLIC
'Substitutes INT 8 handler
'pithandler??(28) holds code
'count?? = new counter value (0 to 65535)
'Output: pithandler??(0) = LSB, pithandler??(1) = MSB of new tick count
' pithandler??(6) = segment, pithandler??(7) = offset of BIOS handler
' pithandler??(2) = count??
cod$ = "00000000000000000000000000000000"
cod$ = cod$ + "50FA2E83060000012E83160200002EA104002E01060A007302"
cod$ = cod$ + "CDF6FBCDF5B020E62058CFCF9050495448"
loadASM pithandler??(), cod$ '(Source code see below)
getintvector 8, sg??, offs??
pithandler??(6) = sg??: pithandler??(7) = offs?? 'load variables
pithandler??(2) = count??
! CLI
putintaddress &HF6, sg??, offs?? 'Relocate original INT 8 handler
'to INT F6
putintvector &HF5, pithandler??(26) 'points to IRET instruction
'(INT F5 will be available to hook
'a routine which needs to be called
'at each tick)
putintvector 8, pithandler??(8) 'Let INT 8 vector point to the entry
'point of the new handler
OUT &H43, &H34 'Set clock frequency
OUT &H40, count?? MOD 256
OUT &H40, count??\256
! STI
END SUB
FUNCTION getpit() PUBLIC AS DWORD
'returns tick count since "setpithandler" was initialized.
DEF SEG = 0
tickptr?? = PEEKI(32)
tickseg?? = PEEKI(34)
DEF SEG = tickseg??
getpit = PEEKL (tickptr??-16)
DEF SEG
END FUNCTION
FUNCTION pitcheck() PUBLIC AS WORD
'Installation check for pithandler
'Output: 1 = pithandler not installed,
' other values: = counter constant in pithandler
DEF SEG = 0
s?? = PEEKI(34): o?? = PEEKI(32)
DEF SEG = s??
Id$ = PEEK$ (o??+38, 4)
IF Id$="PITH" THEN pitcheck = PEEKI (o??-12) ELSE pitcheck = 1
DEF SEG
END FUNCTION
SUB pitfreq (freq) PUBLIC
'Changes timer frequency; freq = frequency in Hz
IF freq<19 OR freq>2000 THEN EXIT SUB 'because Windows does not accept
'higher frequencies
counter?? = &H1234DD\freq
DEF SEG = 0
s?? = PEEKI(34): o?? = PEEKI(32)
DEF SEG = s??
POKEI o??-12, counter?? 'Set counter constant in interrupt handler
DEF SEG
! CLI
OUT &H43, &H34 'Set new clock frequency
OUT &H40, counter?? MOD 256
OUT &H40, counter??\256
! STI
END SUB
SUB resetpithandler () PUBLIC
'retrieves original handler, resets original frequency.
! CLI
getintvector &HF6, sg??, offs??
putintaddress 8, sg??, offs??
OUT &H43, &H34 'Restore original clock frequency
OUT &H40, 0
OUT &H40, 0
! STI
END SUB
==============================
Source code for INT 8 handler:
==============================
Byte Machine code ASM
00 00 00 ;var: Tick count (low)
02 00 00 ;var: Tick count (high)
04 00 00 ;var: counter constant
06 00 00 ;var: real clockticks (low)
08 00 00 ;var: real clockticks (high)
0A 00 00 ;var: sum for real tick count
0C 00 00 ;var: BIOS Handler Segment
0E 00 00 ;var: BIOS Handler Offset
;Byte 10h = Routine Entry point
10 50 PUSH AX
11 FA CLI
12 2E CS: ; update Tick count
13 83 06 00 00 01 ADD WORD [0000], 1
18 2E CS:
19 83 16 02 00 00 ADC WORD [0002], 0
1E 2E CS: ; update real tick counter
1F A1 04 00 MOV AX, [0004]
22 2E CS:
23 01 06 0A 00 ADD WORD [000A], AX
27 73 02 JNC 002B ; overflow?
29 CD F6 INT F6 ; yes, then call original INT 8 ; handler (relocated at INT F6)
2B FB STI
2C CD F5 INT F5 ; INT F5 can be used to hook further
; routines
2E B0 20 MOV AL, 20 ; acknowledge interrupt
30 E6 20 OUT 20, AL ; (may not be necessary ?)
32 58 POP AX
33 CF IRET
34 CF IRET ; repeated at an EVEN byte position
; so the INT F5 vector can point to
; this instruction
35 90 NOP
36 50 49 54 48 ;Signature "PITH" for installation check
(58 bytes)
Comment