The Timer Device


The timer device is a software component that provides a standardized interface to the timing hardware.The 3DO hardware architecture includes a number of clocks and timers.

The timer device has two separate timer units that offer different timing characteristics: the microsecond unit (TIMER_UNIT_USEC) and the vertical blank unit (TIMER_UNIT_VBLANK). The microsecond timer deals with time quantities using seconds and microseconds, while the vertical blank timer counts time in vertical blank intervals. Vertical blank intervals are discussed in more detail in The Vertical Blank Unit. Both units respond to the same commands.

Using either of the timer units, you can do four basic operations:

The following sections describe how to perform these operations with either timer device units.

Note: The timer device currently doesn't provide real-time clocks. That is, the timer starts counting time only when the machine is turned on, it stops counting time when the machine is shut down. Therefore, it is not currently possible to automatically determine the current time of day. This capability may be added to the 3DO architecture in the future.

Working With the Timer Device

Communicating with the two units of the timer device is done using the standard Portfolio I/O commands. To send commands to the timer device, you must complete the following steps:

  1. Open the timer device using the OpenNamedDevice() function.
  2. Create an IOReq structure by calling the CreateIOReq() function.
  3. Initialize an IOInfo structure that specifies the command and parameters to the command.
  4. Send the command to the timer device using either DoIO() or SendIO().

The Microsecond Unit

The microsecond timer unit provides very high-resolution timing. Although it has very high short-term accuracy, its time base can drift slightly over extended periods of time.

This microsecond unit of the timer device deals in time quantities using the TimeVal structure, which is defined as:

typedef struct timeval
{
    int32 tv_Seconds;         /* seconds */
    int32 tv_Microseconds;     /* and microseconds */
} TimeVal;

Reading the Current System Time

To read the current system time, you must initialize an IOInfo structure such as:

IOInfo  ioInfo;
TimeVal tv;

memset(&ioInfo,0,sizeof(ioInfo));
ioInfo.ioi_Command                         = CMD_READ
ioInfo.ioi_Unit                         = TIMER_UNIT_USEC;
ioInfo.ioi_Recv.iob_Buffer = &tv;
ioInfo.ioi_Recv.iob_Len                         = sizeof(tv);

The ioi_Command field is set to CMD_READ, indicating the current system time should be read. ioi_Unit indicates which unit of the timer device to query. ioi_Recv.iob_Buffer points to a TimeVal structure. This pointer is where the timer device stores the current time value. Finally, ioi_Recv.iob_Len holds the size of the TimeVal structure.

Once an I/O operation is performed on the timer device using the above IOInfo, the timer device puts the current system time in the supplied TimeVal structure, and completes the I/O operation by sending your task a message or a signal, depending on how the I/O request was created.

For a high performance way to read the system's microseconds clock, see High-Performance Timing.

Waiting for a Specific Time to Arrive

A common use for the timer device is to provide automatic notification of the passage of time. For example, if you want a given picture to remain on the display for exactly 1 second, you can send a command to the timer device telling it to send you a signal when 1 second has passed. While you are waiting for that second to pass, your task can do other work, such as play music, confident in the fact that the timer device will notify it when the appropriate amount of time has passed.

You can ask the timer device to notify you when a specific time arrives. To do this, you must first ask the system what the current time is by sending the device a CMD_READ. Once you know the current time, you can use the AddTimes() and SubTimes() calls, explained below, to calculate the time to receive a notification. Once you have calculated the time to be notified, you can send the TIMERCMD_DELAYUNTIL command to the timer device. You must initialize the IOInfo structure in the following way:

IOInfo  ioInfo;
TimeVal tv;

memset(&ioInfo,0,sizeof(ioInfo));
ioInfo.ioi_Command                          = TIMERCMD_DELAYUNTIL;
ioInfo.ioi_Unit                         = TIMER_UNIT_USEC;
ioInfo.ioi_Send.iob_Buffer = &tv;
ioInfo.ioi_Send.iob_Len   = sizeof(tv);

The ioi_Command field is set to TIMERCMD_DELAYUNTIL, indicating that the timer will wait until a specific time arrives. ioi_Unit indicates which unit of the timer device to use. ioi_Send.iob_Buffer points to a TimeVal structure. This contains the amount of time to wait. Finally, ioi_Send.iob_Len holds the size of the TimeVal structure.

You can send the I/O request to the timer device using either DoIO() or SendIO(). When using DoIO(), your task is put to sleep until the requested time. If you use SendIO(), then your task is free to continue working while the timer is counting time. When the requested time arrives, the timer device will either send your task the SIGF_IODONE signal, or will send you a message as specified in your I/O request.

Waiting a Specific Amount of Time

Instead of asking the timer to wait until a given time, you can tell it to wait for a fixed amount of time to pass. To achieve this, you follow the procedure in the previous section except that you initialize the IOInfo structure differently. For a specific amount of time, ioi_Command must be set to TIMERCMD_DELAY, and the TimeVal structure you supply must specify an amount of time instead of a certain time.

Getting Repeated Notifications

Often, it is desirable to have a timer automatically generate a signal in regular fixed intervals. The TIMERCMD_METRONOME commands arranges to have a signal sent to your task for an undetermined length of time at a fixed rate.

IOInfo  ioInfo;
TimeVal tv;
int32   signals;

memset(&ioInfo,0,sizeof(ioInfo));
ioInfo.ioi_Command                          = TIMERCMD_METRONOME;
ioInfo.ioi_Unit                         = TIMER_UNIT_USEC;
ioInfo.ioi_CmdOptions     = signals;
ioInfo.ioi_Send.iob_Buffer = &tv;
ioInfo.ioi_Send.iob_Len   = sizeof(tv);

The ioi_Command field is set to TIMERCMD_METRONOME indicating that the timer acts as a metronome, and sends your task signals every time a specified amount of time passes. ioi_Unit indicates which unit of the timer device to use. ioi_CmdOptions specifies the signal mask that the timer device should use when signalling your task. ioi_Send.iob_Buffer points to a TimeVal structure. This contains the amount of time between each signal that the timer device sends your task. Finally, ioi_Send.iob_Len holds the size of the TimeVal structure.

Send the I/O request to the timer device using SendIO(). Once this is done, the timer device will send a signal to your task every time the specified amount of time passes. To stop the timer device from sending these signals, you must abort the I/O request using the AbortIO() call.

The Vertical Blank Unit

The vertical blank timer unit provides a fairly coarse measure of time, but is very stable over long periods of time. It offers a resolution of either 1/60th of a second on NTSC systems or 1/50th of a second on PAL systems. Vertical blanking is a characteristic of raster scan displays, and occurs on a fixed time-scale synchronized with the display hardware.

A vblank is the amount of time it takes for the video beam to perform an entire sweep of the display. Given that displays operate at different refresh rates in NTSC (60 Hz) compared to PAL (50 Hz), the amount of time taken by a vblank varies. Since the vblank unit of the timer device deals with time exclusively in terms of vblank units, waiting for a fixed number of vblanks will take different amounts of time on NTSC and PAL.

The advantages of the vertical blank timer are that it remains stable for very long periods of time; it involves slightly less overhead than the microsecond unit; and it is synchronized with the video beam. Being synchronized with the video beam is very important when creating animation sequences.

This vertical blank unit of the timer device deals in time quantities using the VBlankTimeVal structure, which is defined as:

typedef struct VBlankTimeVal
{
    uint32 vbltv_VBlankHi32;  * upper 32 bits of vblank counter */
    uint32 vbltv_VBlankLo32;    /* lower 32 bits of vblank counter */
} VBlankTimeVal;

Vblanks are counted using a 64-bit counter. This is represented in two 32-bit words. The upper-32 bits, which are the most significant, are stored in the vbltv_VBlankHi32 field, while the lower-32 bits are stored in the vbltv_VBlankLo32 field.

Reading the Current System Time

To read the current system time, you must initialize an IOInfo structure such as:

IOInfo       ioInfo;
VBlankTimeVal vbltv;

memset(&ioInfo,0,sizeof(ioInfo));
ioInfo.ioi_Command                        = CMD_READ
ioInfo.ioi_Unit                        = TIMER_UNIT_VBLANK;
ioInfo.ioi_Recv.iob_Buffer                        = &vbltv;
ioInfo.ioi_Recv.iob_Len                        = sizeof(vbltv);

ioi_Command is set to CMD_READ, indicating that the current system time should be read. ioi_Unit indicates which unit of the timer device to query. ioi_Recv.iob_Buffer points to a VBlankTimeVal structure. This is where the timer device stores the current vblank count. Finally, ioi_Recv.iob_Len holds the size of the VBlankTimeVal structure.

Once the I/O is complete, the supplied VBlankTimeVal structure is filled with the current vblank count. Given that there are either 50 or 60 vblanks per second, over 800 days worth of vblanks can be stored in vbltv_VBlankLo32. Whenever vbltv_VBlankLo32 exceeds the maximum value it can contain (2^32 - 1), then the value of vbltv_VBlankHi32 is incremented by 1. This means that the VBlankTimeVal structure being used to store vblank counts can hold up to (2^32 * 800) days, which is probably longer than the time remaining before the sun goes supernova.

Waiting for a Specific Time

Similar to the microsecond unit, the vblank unit can wait for a specific time. You specify this time in terms of vblanks. Unlike the microsecond unit, you do not specify the amount of time to wait using a TimeVal structure. Instead, set the ioi_Offset field of the IOInfo structure to the vblank count.

IOInfo ioInfo;

memset(&ioInfo,0,sizeof(ioInfo));
ioInfo.ioi_Command                = TIMERCMD_DELAYUNTIL;
ioInfo.ioi_Unit                = TIMER_UNIT_VBLANK;
ioInfo.ioi_Offset                = vblankCountToWaitUntil;

Waiting for a Specific Amount of Time

The vblank unit can wait for a given number of vblanks, that is, a specific amount of time. This is done by initializing an IOInfo structure such as:

IOInfo ioInfo;

memset(&ioInfo,0,sizeof(ioInfo));
ioInfo.ioi_Command                 = TIMERCMD_DELAY;
ioInfo.ioi_Unit                 = TIMER_UNIT_VBLANK;
ioInfo.ioi_Offset                 = numberOfVBlanksToWait;

It is important to understand that the timer counts vblanks in a very strict way. Whenever the video beam reaches a known location on the display, the vblank counter is incremented. So if you ask the timer to wait for 1 vblank while the beam is near the trigger location, the I/O request will be returned in less than 1/60th or 1/50th of a second.

The WaitVBL() function is a wrapper function that initializes an IOInfo structure using the TIMERCMD_DELAY command, and sends it to the timer device.

Getting Repeated Notifications

Similar to the microsecond unit, the vblank unit can automatically send you signals at specified intervals. You specify the interval time in terms of vblanks. Unlike with the microsecond unit, you do not specify the amount of time between signals using a TimeVal structure. Instead, set the ioi_Offset field of the IOInfo structure to the vblank count between signals.

IOInfo  ioInfo;
int32   signals;

memset(&ioInfo,0,sizeof(ioInfo));
ioInfo.ioi_Command                          = TIMERCMD_METRONOME;
ioInfo.ioi_Unit                         = TIMER_UNIT_VBLANK;
ioInfo.ioi_CmdOptions     = signals;
ioInfo.ioi_Offset        = vblanksBetweenSignals;

You should send the I/O request to the timer device using SendIO(). Once this is done, the timer device will send a signal to your task every time the specified number of vblanks occurs. To stop the timer device from sending you these signals, you must abort the I/O request using the AbortIO() call.

High-Performance Timing

It is sometimes necessary to measure very short time intervals with very high accuracy. This is especially useful when trying to measure the performance of various pieces of code. Although using the timer device and the CMD_READ command gives fairly accurate readings, the overhead involved in doing device I/O is often enough to skew the results of small time intervals.

Portfolio provides the SampleSystemTime() and SampleSystemTimeTV() functions that allow low-overhead sampling of the system clock. These functions are based on the same hardware clock as the microsecond unit of the timer device, and so refer to the same time base.

SampleSystemTime() returns the current seconds counter of the system clock. The function also returns the current microseconds counter in the R1 CPU register of the ARM processor. This value is only available if you program in assembly language. To get both values in C, you must use the SampleSystemTimeTV() function, which puts the current values of the seconds and microseconds counters in the TimeVal structure that you supply.

Time Arithmetic

The following calls calculate or compare time values.

Simplified Timer Device Interface

The following calls create I/O requests and wait fixed amounts of time. These are simple convenience calls that interface to the timer device for you. All of these routines use the microsecond timer unit.