Page 1 of 1

EE timers

Posted: Thu Jul 28, 2005 5:01 pm
by shawn_t
I've put together a simple project in an effort to illustrate the use of the EE timer registers. The code is pretty thoroughly documented, but I will explain them a little anyway...Please let me know if you have any questions or concerns.

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)
These allow for some very accurate timer configurations that are accurate in the microsecond (usec) range instead of the usual millisecond (msec) range:
  • 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
The problem is that the Tn_COUNT registers are only 16 bit and therefore they overflow in 2^16 or 65536 ticks. It is easy to compute how long before the Tn_COUNT register will overflow for each of the settings:
  • 0b00: 65536 / 147,456,000 = 0.00044 seconds
    0b01: 65536 / 9,216,000 = 0.00711 seconds
    0b10: 65536 / 576,000 = 0.11378 seconds
These relatively short overflow times usually result in developers using the H-BLNK mode which is much less accurate but overflows for NTSC in: 65536 / 15734 = 4.156 seconds. However, I don't really like using the H-BLNK because it is not as accurate, depends upon the graphics mode and requires that the screen is refreshing.

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	&#40;&#40;volatile unsigned long*&#41;0x10000000&#41;
#define T0_MODE		&#40;&#40;volatile unsigned long*&#41;0x10000010&#41;
#define T0_COMP		&#40;&#40;volatile unsigned long*&#41;0x10000020&#41;
#define T0_HOLD		&#40;&#40;volatile unsigned long*&#41;0x10000030&#41;

#define T1_COUNT	&#40;&#40;volatile unsigned long*&#41;0x10000800&#41;
#define T1_MODE		&#40;&#40;volatile unsigned long*&#41;0x10000810&#41;
#define T1_COMP		&#40;&#40;volatile unsigned long*&#41;0x10000820&#41;
#define T1_HOLD		&#40;&#40;volatile unsigned long*&#41;0x10000830&#41;

// Note! T2 and T3 don't have a Tn_HOLD register!
// ----------------------------------------------
#define T2_COUNT	&#40;&#40;volatile unsigned long*&#41;0x10001000&#41;
#define T2_MODE		&#40;&#40;volatile unsigned long*&#41;0x10001010&#41;
#define T2_COMP		&#40;&#40;volatile unsigned long*&#41;0x10001020&#41;

#define T3_COUNT	&#40;&#40;volatile unsigned long*&#41;0x10001800&#41;
#define T3_MODE		&#40;&#40;volatile unsigned long*&#41;0x10001810&#41;
#define T3_COMP		&#40;&#40;volatile unsigned long*&#41;0x10001820&#41;

#define Tn_MODE&#40;CLKS,GATE,GATS,GATM,ZRET,CUE,CMPE,OVFE,EQUF,OVFF&#41; \
    &#40;u32&#41;&#40;&#40;u32&#41;&#40;CLKS&#41; | &#40;&#40;u32&#41;&#40;GATE&#41; << 2&#41; | \
	&#40;&#40;u32&#41;&#40;GATS&#41; << 3&#41; | &#40;&#40;u32&#41;&#40;GATM&#41; << 4&#41; | \
	&#40;&#40;u32&#41;&#40;ZRET&#41; << 6&#41; | &#40;&#40;u32&#41;&#40;CUE&#41; << 7&#41; | \
	&#40;&#40;u32&#41;&#40;CMPE&#41; << 8&#41; | &#40;&#40;u32&#41;&#40;OVFE&#41; << 9&#41; | \
	&#40;&#40;u32&#41;&#40;EQUF&#41; << 10&#41; | &#40;&#40;u32&#41;&#40;OVFF&#41; << 11&#41;&#41;

#define kBUSCLK				&#40;147456000&#41;
#define kBUSCLKBY16			&#40;kBUSCLK / 16&#41;
#define kBUSCLKBY256		&#40;kBUSCLK / 256&#41;
#define kHBLNK_NTSC			&#40;15734&#41;
#define kHBLNK_PAL			&#40;15625&#41;
#define kHBLNK_DTV480p		&#40;31469&#41;
#define kHBLNK_DTV1080i		&#40;33750&#41;

enum
&#123;
	kINTC_GS,
	kINTC_SBUS,
	kINTC_VBLANK_START,
	kINTC_VBLANK_END,
	kINTC_VIF0,
	kINTC_VIF1,
	kINTC_VU0,
	kINTC_VU1,
	kINTC_IPU,
	kINTC_TIMER0,
	kINTC_TIMER1
&#125;; 

// =====================
// Static Timer Variable
// =====================
static int		s_tnInterruptID = -1;
static u64		s_tnInterruptCount = 0;

// =======================
// Time Interrupt Callback
// =======================
int
tnTimeInterrupt&#40;int ca&#41;
&#123;
	s_tnInterruptCount++;

	// A write to the overflow flag will clear the overflow flag
	// ---------------------------------------------------------
	*T1_MODE |= &#40;1 << 11&#41;;

	return -1;
&#125;

// ==============
// Time functions
// ==============
void
tnTimeInit&#40;void&#41;
&#123;
	// ============================================================
	// 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&#58;
	//   147,456,000 / 256 = 576,000 Hz
	// This implies that the accuracy of this timer is&#58;
	//   1 / 576,000 = 0.0000017361 seconds &#40;~1.74 usec!&#41;
	// The Tn_COUNT registers are 16 bit and overflow in&#58;
	//   1 << 16 = 65536 seconds
	// This implies that our timer will overflow in&#58;
	//   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&#58;
	//   147,456,000 / 16 = 9,216,000 Hz
	// which implies an accuracy of&#58;
	//   1 / 9,216,000 = 0.0000001085 seconds &#40;0.11 usec!&#41;
	// However, the timer will overflow in&#58;
	//   65536 / 9,216,000 = 0.0071 seconds &#40;7.1 msec&#41;
	// 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&#40;kINTC_TIMER1, tnTimeInterrupt, 0&#41;;
	EnableIntc&#40;kINTC_TIMER1&#41;;

	// Initialize the timer registers
	// CLKS&#58; 0x02 - 1/256 of the BUSCLK &#40;0x01 is 1/16th&#41;
	//  CUE&#58; 0x01 - Start/Restart the counting
	// OVFE&#58; 0x01 - An interrupt is generated when an overflow occurs
	// --------------------------------------------------------------
	*T1_COUNT = 0;
	*T1_MODE = Tn_MODE&#40;0x02, 0, 0, 0, 0, 0x01, 0, 0x01, 0, 0&#41;;

	s_tnInterruptCount = 0;
&#125;

u64
tnTime&#40;void&#41;
&#123;
	u64			t;

	// Tn_COUNT is 16 bit precision. Therefore, each
	// <s_tnInterruptCount> is 65536 ticks
	// ---------------------------------------------
	t = *T1_COUNT + &#40;s_tnInterruptCount << 16&#41;;

	t = t * 1000000 / kBUSCLKBY256;

	return t;
&#125;

void
tnTimeFini&#40;void&#41;
&#123;
	// Stop the timer
	// --------------
	*T1_MODE = 0x0000;

	// Disable the interrupt
	// ---------------------
	if &#40;s_tnInterruptID >= 0&#41;
	&#123;
		DisableIntc&#40;kINTC_TIMER1&#41;;
		RemoveIntcHandler&#40;kINTC_TIMER1, s_tnInterruptID&#41;;
		s_tnInterruptID = -1;
	&#125;

	s_tnInterruptCount = 0;
&#125;

// ========================
// Main program entry point
// ========================
int
main&#40;&#41;
&#123;
	u64			begin, end;
	int			i;
	float		fval, a, b;

	init_scr&#40;&#41;;
	scr_printf&#40;"PS2 Timer Test!\n"&#41;;

	tnTimeInit&#40;&#41;;

	a = 1.2345f;
	b = 3.1415f;
	begin = tnTime&#40;&#41;;
	for &#40;i=0; i<1000000; i++&#41;
	&#123;
		fval = a * b;
	&#125;
	end = tnTime&#40;&#41;;

	scr_printf&#40;"1000000 multiplies took %ld usec\n", end - begin&#41;;

	tnTimeFini&#40;&#41;;

	return 0;
&#125;

Posted: Thu Jul 28, 2005 5:23 pm
by cheriff
Nice!
Up to now i've just been keeping count of vsync callbacks to keep time, this is much nicer!
Now it makes me wonder why I didn't make use of the EE timers...

Posted: Wed Aug 24, 2005 6:34 pm
by EEUG
...did anyone manage to get T2 and T3 timers working? On my site any write attempt to T2_MODE/T3_MODE causes system hang :(...

Posted: Wed Aug 24, 2005 9:02 pm
by thjelvar
The kernel prohibits access to page on which the T2_xxx and T3_xxx registers are located when the cpu is non user mode. Also the T3 counter is used by the kernel itself for the ...Alarm syscalls, so you might not wan't to mess around with it.

Posted: Wed Aug 24, 2005 9:55 pm
by EEUG
...yes, I suspected that. Thank you for clearing this up :)...

Re: Functions that implement (seconds, minutes, & hours

Posted: Sun Jul 09, 2006 12:46 am
by DeRieux
Shawn_t you can consider adding or looking over this code addition
it adds functions to you source code, that directly makes it become
a timer function that everyone (including my self) has been looking for....

Better than using the

Code: Select all

u32 cpu_ticks&#40;void&#41;
function

*note with these extended void functions instead of passing 3,600 seconds for 1 hour, it will
just call wait_seconds(1) 3,600 times for an hour or 60 times
for a minute, or just 1 time for a second ;


Code: Select all

/* You can include the following in the present source */
	
	/* Code addition made by William */

/* In addition to your code telling you how much time
    an operation took, this will also allow you to implement it as a basic timer */

#define ONE_SECOND 1356155 /* Relative Conversion of a Second to a Usec &#40;not Accurate&#41;  anyone who finds a value producing a higher-resolution of Ticks, please send it aloing &#40;I'd like to know&#41; */

void wait_seconds&#40;double seconds&#41;;
void wait_minutes&#40;double minutes&#41;;
void wait_hours&#40;double hours&#41;;

void wait_seconds&#40;double seconds&#41;
&#123;
   int TimeCheck = &#40;seconds*ONE_SECOND&#41;; /* Relative Conversion of a Second to a Usec */

   tnTimeInit&#40;&#41;;  
     
    do&#123;&#125;
    while &#40;tnTime&#40;&#41; < TimeCheck&#41;;  
    
    tnTimeFini&#40;&#41;;
&#125;

void wait_minutes&#40;double minutes&#41;
&#123;
	int i;
	for&#40;i=0;i<&#40;minutes*60&#41;;i++&#41;
	&#123;
	     wait_seconds&#40;1&#41;;
	&#125;   
&#125;

void wait_hours&#40;double hours&#41;
&#123;
   int i;
	for&#40;i=0;i<&#40;hours*3600&#41;;i++&#41;
	&#123;
	     wait_seconds&#40;1&#41;;
	&#125;
&#125; 

William

Edit: Changed to double datatype to allow say 0.5 seconds or 1.5 seconds, etc, etc

Posted: Mon Feb 08, 2010 1:49 am
by protomank
I've hacked the SDL_Delay to use your code. It works like a charm. I hope I can get this up to the SDL svn once I clean and integrate the code better. Thanks a lot!