 |
forums.ps2dev.org Homebrew PS2, PSP & PS3 Development Discussions
|
| View previous topic :: View next topic |
| Author |
Message |
shawn_t
Joined: 26 Jul 2005 Posts: 11
|
Posted: Thu Jul 28, 2005 5:01 pm Post subject: EE timers |
|
|
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: |
#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;
}
|
Last edited by shawn_t on Thu Jul 28, 2005 5:59 pm; edited 2 times in total |
|
| Back to top |
|
 |
cheriff Regular
Joined: 23 Jun 2004 Posts: 262 Location: Sydney.au
|
Posted: Thu Jul 28, 2005 5:23 pm Post subject: |
|
|
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... _________________ Damn, I need a decent signature! |
|
| Back to top |
|
 |
EEUG
Joined: 13 May 2005 Posts: 136 Location: The Netherlands
|
Posted: Wed Aug 24, 2005 6:34 pm Post subject: |
|
|
| ...did anyone manage to get T2 and T3 timers working? On my site any write attempt to T2_MODE/T3_MODE causes system hang :(... |
|
| Back to top |
|
 |
thjelvar
Joined: 24 Aug 2005 Posts: 1
|
Posted: Wed Aug 24, 2005 9:02 pm Post subject: |
|
|
| 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. |
|
| Back to top |
|
 |
EEUG
Joined: 13 May 2005 Posts: 136 Location: The Netherlands
|
Posted: Wed Aug 24, 2005 9:55 pm Post subject: |
|
|
| ...yes, I suspected that. Thank you for clearing this up :)... |
|
| Back to top |
|
 |
DeRieux

Joined: 06 Jul 2006 Posts: 9
|
Posted: Sun Jul 09, 2006 12:46 am Post subject: Re: Functions that implement (seconds, minutes, & hours |
|
|
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: | | u32 cpu_ticks(void) | 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: | /* 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 (not Accurate) anyone who finds a value producing a higher-resolution of Ticks, please send it aloing (I'd like to know) */
void wait_seconds(double seconds);
void wait_minutes(double minutes);
void wait_hours(double hours);
void wait_seconds(double seconds)
{
int TimeCheck = (seconds*ONE_SECOND); /* Relative Conversion of a Second to a Usec */
tnTimeInit();
do{}
while (tnTime() < TimeCheck);
tnTimeFini();
}
void wait_minutes(double minutes)
{
int i;
for(i=0;i<(minutes*60);i++)
{
wait_seconds(1);
}
}
void wait_hours(double hours)
{
int i;
for(i=0;i<(hours*3600);i++)
{
wait_seconds(1);
}
} |
William
Edit: Changed to double datatype to allow say 0.5 seconds or 1.5 seconds, etc, etc _________________ William DeRieux -
Have just started out using ps2sdk
Have made my Laptop, Linux Only
and am in the process of putting the pieces back together (oops!)
Email me at WilliamDeRieux@gmail.com |
|
| Back to top |
|
 |
protomank
Joined: 18 Dec 2008 Posts: 64 Location: Porto Alegre, RS, Brazil
|
Posted: Mon Feb 08, 2010 1:49 am Post subject: |
|
|
| 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! |
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|