There are 4 16 bit timer registers on the PS2 that can be configured by the user using the Tn_MODE register. Configuration can control not only the number of times the Tn_COUNT register triggers per second, but also a comparison interrupt and an overflow interrupt. The Tn_MODE() macro in the source code allows you to easily configure the Tn_MODE register for n:0-3. The lowest 2 bits are the most important as they control how many times the Tn_COUNT register ticks per second. The options are:
- 0b00: BUSCLK (147,456,000 cycles per second)
 0b01: 1/16 BUSCLK (9,216,000 cycles per second)
 0b10: 1/256 BUSCLK (576,000 cycles per second)
 0b11: Externel clock (H-BLNK rate depends upon graphics mode)
- 0b00: 1 / 147,456,000 = 0.00678 usec
 0b01: 1 / 9,216,000 = 0.1085 usec
 0b10: 1 / 576,000 = 1.7361 usec
 0b11: depends upon video mode
- 0b00: 65536 / 147,456,000 = 0.00044 seconds
 0b01: 65536 / 9,216,000 = 0.00711 seconds
 0b10: 65536 / 576,000 = 0.11378 seconds
Instead, I use the overflow functionality of the Tn_MODE register to keep track of the number of overflows that occur using an interrupt function. You can also use the Tn_COMP register and the comparison flag of the Tn_MODE register to count comparisons to whatever value you want, but I opt to use the overflow in this implementation.
This implementation uses the 0b10 (0x02) mode which is accurate to ~1.7 usec and overflows every ~0.11 seconds. I think this is very accurate and requires little overhead with interrupts occuring only around 10 times per second.
The tnTimeInit() function below sets up the interrupt function and the Tn_MODE. My implementation uses the T1_COUNT and T1_MODE registers, but you can choose to use any of T0, T1, T2 or T3. It may be a good idea to set up two registers of different accuracies if you have to get to some really detailed timing!
Code: Select all
#include <stdio.h>
#include <tamtypes.h>
#include <kernel.h>
#include <debug.h>
// ================================================
// Defines and Enumerations for timer related tasks
// ================================================
#define T0_COUNT	((volatile unsigned long*)0x10000000)
#define T0_MODE		((volatile unsigned long*)0x10000010)
#define T0_COMP		((volatile unsigned long*)0x10000020)
#define T0_HOLD		((volatile unsigned long*)0x10000030)
#define T1_COUNT	((volatile unsigned long*)0x10000800)
#define T1_MODE		((volatile unsigned long*)0x10000810)
#define T1_COMP		((volatile unsigned long*)0x10000820)
#define T1_HOLD		((volatile unsigned long*)0x10000830)
// Note! T2 and T3 don't have a Tn_HOLD register!
// ----------------------------------------------
#define T2_COUNT	((volatile unsigned long*)0x10001000)
#define T2_MODE		((volatile unsigned long*)0x10001010)
#define T2_COMP		((volatile unsigned long*)0x10001020)
#define T3_COUNT	((volatile unsigned long*)0x10001800)
#define T3_MODE		((volatile unsigned long*)0x10001810)
#define T3_COMP		((volatile unsigned long*)0x10001820)
#define Tn_MODE(CLKS,GATE,GATS,GATM,ZRET,CUE,CMPE,OVFE,EQUF,OVFF) \
    (u32)((u32)(CLKS) | ((u32)(GATE) << 2) | \
	((u32)(GATS) << 3) | ((u32)(GATM) << 4) | \
	((u32)(ZRET) << 6) | ((u32)(CUE) << 7) | \
	((u32)(CMPE) << 8) | ((u32)(OVFE) << 9) | \
	((u32)(EQUF) << 10) | ((u32)(OVFF) << 11))
#define kBUSCLK				(147456000)
#define kBUSCLKBY16			(kBUSCLK / 16)
#define kBUSCLKBY256		(kBUSCLK / 256)
#define kHBLNK_NTSC			(15734)
#define kHBLNK_PAL			(15625)
#define kHBLNK_DTV480p		(31469)
#define kHBLNK_DTV1080i		(33750)
enum
{
	kINTC_GS,
	kINTC_SBUS,
	kINTC_VBLANK_START,
	kINTC_VBLANK_END,
	kINTC_VIF0,
	kINTC_VIF1,
	kINTC_VU0,
	kINTC_VU1,
	kINTC_IPU,
	kINTC_TIMER0,
	kINTC_TIMER1
}; 
// =====================
// Static Timer Variable
// =====================
static int		s_tnInterruptID = -1;
static u64		s_tnInterruptCount = 0;
// =======================
// Time Interrupt Callback
// =======================
int
tnTimeInterrupt(int ca)
{
	s_tnInterruptCount++;
	// A write to the overflow flag will clear the overflow flag
	// ---------------------------------------------------------
	*T1_MODE |= (1 << 11);
	return -1;
}
// ==============
// Time functions
// ==============
void
tnTimeInit(void)
{
	// ============================================================
	// I am using 1/256 of the BUSCLK below in the Tn_MODE register
	// which means that the timer will count at a rate of:
	//   147,456,000 / 256 = 576,000 Hz
	// This implies that the accuracy of this timer is:
	//   1 / 576,000 = 0.0000017361 seconds (~1.74 usec!)
	// The Tn_COUNT registers are 16 bit and overflow in:
	//   1 << 16 = 65536 seconds
	// This implies that our timer will overflow in:
	//   65536 / 576,000 = 0.1138 seconds
	// I use an interrupt to recognize this overflow and increment
	// the <s_tnInterruptCount> variable so I can easily compute 
	// the total time. This results in a very accurate timer that
	// is also very efficient. It is possible to have an even more
	// accurate timer by modifying the Tn_MODE, but at the expense
	// of having to call the overflow interrupt more frequently.
	// For example, if you wanted to use 1/16 of the BUSCLK, the
	// timer would count at a rate of:
	//   147,456,000 / 16 = 9,216,000 Hz
	// which implies an accuracy of:
	//   1 / 9,216,000 = 0.0000001085 seconds (0.11 usec!)
	// However, the timer will overflow in:
	//   65536 / 9,216,000 = 0.0071 seconds (7.1 msec)
	// meaning, the interrupt would be called more then 140 times a
	// second. For my purposes the accuracy of ~1.74 usec is fine!
	// ============================================================
	// Disable T1_MODE
	// ---------------
	*T1_MODE = 0x0000;
	// Initialize the overflow interrupt handler.
	// -----------------------------------------
	s_tnInterruptID = AddIntcHandler(kINTC_TIMER1, tnTimeInterrupt, 0);
	EnableIntc(kINTC_TIMER1);
	// Initialize the timer registers
	// CLKS: 0x02 - 1/256 of the BUSCLK (0x01 is 1/16th)
	//  CUE: 0x01 - Start/Restart the counting
	// OVFE: 0x01 - An interrupt is generated when an overflow occurs
	// --------------------------------------------------------------
	*T1_COUNT = 0;
	*T1_MODE = Tn_MODE(0x02, 0, 0, 0, 0, 0x01, 0, 0x01, 0, 0);
	s_tnInterruptCount = 0;
}
u64
tnTime(void)
{
	u64			t;
	// Tn_COUNT is 16 bit precision. Therefore, each
	// <s_tnInterruptCount> is 65536 ticks
	// ---------------------------------------------
	t = *T1_COUNT + (s_tnInterruptCount << 16);
	t = t * 1000000 / kBUSCLKBY256;
	return t;
}
void
tnTimeFini(void)
{
	// Stop the timer
	// --------------
	*T1_MODE = 0x0000;
	// Disable the interrupt
	// ---------------------
	if (s_tnInterruptID >= 0)
	{
		DisableIntc(kINTC_TIMER1);
		RemoveIntcHandler(kINTC_TIMER1, s_tnInterruptID);
		s_tnInterruptID = -1;
	}
	s_tnInterruptCount = 0;
}
// ========================
// Main program entry point
// ========================
int
main()
{
	u64			begin, end;
	int			i;
	float		fval, a, b;
	init_scr();
	scr_printf("PS2 Timer Test!\n");
	tnTimeInit();
	a = 1.2345f;
	b = 3.1415f;
	begin = tnTime();
	for (i=0; i<1000000; i++)
	{
		fval = a * b;
	}
	end = tnTime();
	scr_printf("1000000 multiplies took %ld usec\n", end - begin);
	tnTimeFini();
	return 0;
}
