DEFINT A-Z
DECLARE FUNCTION TimerAlarmAlloc% ()
'$INCLUDE: '\Prog\Forward\LIB\MTimer\MTimer.bi'
'$INCLUDE: '\Prog\Forward\LIB\MTimer\MTimerA.bi'
'   Filename: MTimer.bas
'      Title: DOS Timer Using PIT
'Description: Microsecond timer library using DOS timer and PIT.
'     Author: Antoni Gual [AG] and Peter Fedorow [PF]
'    Contact: AG - agual AT eic.ictnet.es
'    Contact: PF - fedorowp AT yahoo.com / fedorowp AT earthlink.net
'  Copyright: 2003 Antoni Gual, 2003 Peter Fedorow
'    Started: 2002 Mar 15
'Last Change: 2003 May 30 Fri
'    History: 2002 Mar 15      [AG] Initial write..
'    History: 2003 May 25 Sun  [PF] Removed non-Windows compatible function.
'    History: 2003 May 25 Sun  [PF] Cleaned up for library use.
'    History: 2003 May 26 Mon  [PF] More documentation and cleanup.
'    History: 2003 May 26 Mon  [PF] Make PITReadTicks ASM so can make safe.
'    History: 2003 May 30 Fri  [PF] Added handle valid check in TimerAlarmPoll.
'    History: 2003 May 30 Fri  [PF] Moved alarm init into TimerAlarmsetTicks.

	CONST False = 0
	CONST True = NOT False



'----------------------------------------------------------------------------
'Purpose
'-------
'Delay for intervals smaller than the QB'S TIMER resolution and handle
'midnight rollovers.
'
'Function
'--------
'The PIT (Programmable Interval Timer) was the chip that clocked the
'software timer and generates the speaker beeps and DRAM refresh.  Today its
'functions are emulated inside the chipset.
'A counter in the PIT starts at 65535 and counts down at a frequency of
'1,193,181 Hz.  When it reaches zero it resets itself and issues an
'interrupt, (it happens approximately 18.2 times per second), used by DOS to
'update the software timer.  At any time the PIT register can be read for
'increased accuracy.
'
'In a DOS box in Win32, almost everything is emulated. The DOS timer update
'is not done at the same moment when PIT counter becomes 0, so you can have
'readings 1/18.2 second lower than they should be.
'
'MicroTimer is Windows compatible and gives times in PIT units (1/1,193,181
'of a second).
'
'Maximum possible delay is 29.99 minutes due to the capacity of the LONG
'variable type.
'
'The TimerDelay routine is the base to implement your delays.  The routine
'deals with timer rollovers.  Delays can be nested by using different delay
'variables.
'----------------------------------------------------------------------------


'Notes
'-----
'PICTicks was not disabling interrupts before port IO.  If the timer ran
'down and called the clock interrupt the resulting read would be broken.
'I rewrote the routine in ASM and added the necessary interrupt
'disable/enable commands.
'
'This rewritten version averages .0169 milliseconds per TimerRead call on my
'computer.  The original version averaged .0153 milliseconds per call.
'
'Rewriting TimerRead in ASM should easily at least double its speed as
'QB/BC7 generates very inefficient code for it.


	DIM SHARED TimeStart&(1 TO TimerHandles)
	DIM SHARED TimeEnd&(1 TO TimerHandles)
	DIM SHARED THandleUsed(1 TO TimerHandles)


'To Do
'-----

FUNCTION TimerAlarmAlloc ()

	DO
		Thandle = Thandle + 1
		IF NOT THandleUsed(Thandle) THEN
			Found = True
		END IF
	LOOP WHILE (NOT Found) AND (Thandle <= TimerHandles)

	IF Found THEN
		THandleUsed(Thandle) = True
	ELSE
		PRINT "*** MTimer.TimerAlarmAlloc: Insufficent timer handles, increase TimerHandles. ***"
		STOP
	END IF


	TimerAlarmAlloc = Thandle

END FUNCTION

SUB TimerAlarmFree (Thandle)

	THandleUsed(Thandle) = False
	Thandle = 0

END SUB

FUNCTION TimerAlarmPoll (Thandle)
'Function returns True when delay elapsed.

	SHARED TimeStart&()
	SHARED TimeEnd&()


	IF Thandle = 0 THEN
		TimerAlarmPoll = True

	ELSE

		CurrentTime& = TimerRead&
		IF (TimeEnd&(Thandle) > TimeStart&(Thandle)) OR (CurrentTime& < TimeStart&(Thandle)) THEN
			IF CurrentTime& >= TimeEnd&(Thandle) THEN
				TimerAlarmPoll = True
				'Free the no longer needed handle.
				TimerAlarmFree Thandle
			END IF
		END IF

	END IF

END FUNCTION

FUNCTION TimerAlarmSet (DelayInSecs#)
	
	TimerAlarmSet = TimerAlarmSetTicks(DelayInSecs# * PITClockFreq#)

END FUNCTION

FUNCTION TimerAlarmSetTicks (DelayInTicks&)

	CONST tr& = &H3FFFFFFF
	CONST tc& = &H40000000

	SHARED TimeStart&()
	SHARED TimeEnd&()


	TimeStart& = TimerRead&
	TimeEnd& = DelayInTicks&

	T& = TimeEnd&
	TimeEnd& = (TimeStart& AND tr&) + (TimeEnd& AND tr&)
	C = ((TimeStart& AND tc&) = tc&) + ((T& AND tc&) = tc&) + ((TimeEnd& AND tc&) = tc&)
	IF C AND 1 THEN
		TimeEnd& = TimeEnd& OR tc&
	END IF


	Thandle = TimerAlarmAlloc

	TimeStart&(Thandle) = TimeStart&
	TimeEnd&(Thandle) = TimeEnd&


	TimerAlarmSetTicks = Thandle

END FUNCTION

SUB TimerDelay (DelayInSecs#)
'Waits for a specific amount of time.

	Thandle = TimerAlarmSet(DelayInSecs#)

	DO WHILE TimerAlarmPoll(Thandle)
	LOOP

END SUB

SUB TimerDelayTicks (DelayInTicks&)
'Waits for a specific amount of time.

	Thandle = TimerAlarmSetTicks(DelayInTicks&)

	DO WHILE TimerAlarmPoll(Thandle)
	LOOP

END SUB

FUNCTION TimerRead& ()
'Returns time in PIT ticks modulo 30 minutes.
'Slow but safe to use in both a DOS box under Windows and in plain DOS.

	STATIC TimerValuePrev&
	STATIC TimerHiRollOver


	DEF SEG = &H0

	DO
		'BIOS timer seconds count.
		TimerHi = PEEK(&H46D)  'High byte.
		TimerLo = PEEK(&H46C)  'Low byte.

		PITReadTicks TicksHi, TicksLo


		TimerValue& = 256& * (256& * (1& + TimerLo + 256& * (TimerHi AND &H7F)) - TicksLo) - TicksHi

		RollOverFlag = (TimerHi AND &H80) <> TimerHiRollOver

	LOOP UNTIL RollOverFlag OR (TimerValue& > TimerValuePrev&)

	DEF SEG


	TimerHiRollOver = TimerHi AND &H80

	SWAP TimerValue&, TimerValuePrev&


	TimerRead& = TimerValuePrev&

END FUNCTION

