[← Home](../README.md) · [Devices](README.md) # timer.device — Timing, Delays, and High-Resolution Timestamps ## Overview `timer.device` provides all timing services on AmigaOS: delays, system clock queries, and high-resolution timestamps. It interfaces with two independent hardware sources — the **CIA timers** (microsecond resolution) and the **vertical blank interrupt** (frame-rate resolution). --- ## Units | Unit | Constant | Resolution | Clock Source | Use Case | |---|---|---|---|---| | 0 | `UNIT_MICROHZ` | ~1.4 µs (E-clock tick) | CIA-A Timer A | Short, precise delays | | 1 | `UNIT_VBLANK` | ~20 ms (PAL) / ~16.7 ms (NTSC) | VBlank interrupt | Long delays, low CPU overhead | | 2 | `UNIT_ECLOCK` | ~1.4 µs | CIA-B Timer A | Highest resolution timing (OS 2.0+) | | 3 | `UNIT_WAITUNTIL` | absolute time | System clock | Wait until specific wall-clock time | | 4 | `UNIT_WAITECLOCK` | E-clock absolute | CIA | Wait until specific E-clock value | ### Which Unit to Use? ```mermaid flowchart TD Q["How long is the delay?"] --> SHORT["< 100 ms"] Q --> LONG["> 100 ms"] SHORT --> MICRO["UNIT_MICROHZ
or UNIT_ECLOCK"] LONG --> VBLANK["UNIT_VBLANK
(lower CPU overhead)"] Q --> MEASURE["Need to measure
elapsed time?"] MEASURE --> ECLOCK["ReadEClock()"] ``` --- ## Hardware Foundation ### CIA Timer Internals The timing hardware lives in the two CIA (Complex Interface Adapter) chips: | CIA | Base | Timer | E-Clock Frequency | |---|---|---|---| | CIA-A | `$BFE001` | Timer A, Timer B | 709,379 Hz (PAL) / 715,909 Hz (NTSC) | | CIA-B | `$BFD000` | Timer A, Timer B | Same | The **E-clock** is derived from the system clock ÷ 10 (PAL: 7,093,790 / 10 = 709,379 Hz). Each tick is ~1.4 µs. ```c /* E-clock ticks per second: */ #define ECLOCK_PAL 709379 #define ECLOCK_NTSC 715909 /* Example: 100 ms delay = 70,938 ticks (PAL) */ ``` ### VBlank Timing UNIT_VBLANK piggybacks on the vertical blank interrupt — one tick per video frame: | Standard | VBlank Rate | Resolution | |---|---|---| | PAL | 50 Hz | 20.0 ms | | NTSC | 60 Hz | 16.7 ms | --- ## Structures ```c /* devices/timer.h — NDK39 */ struct timeval { ULONG tv_secs; /* seconds */ ULONG tv_micro; /* microseconds (0–999999) */ }; struct timerequest { struct IORequest tr_node; struct timeval tr_time; }; /* sizeof(timerequest) = sizeof(IORequest) + 8 */ ``` ### EClockVal (OS 2.0+) ```c struct EClockVal { ULONG ev_hi; /* high 32 bits of 64-bit tick counter */ ULONG ev_lo; /* low 32 bits */ }; ``` --- ## Opening timer.device ```c struct MsgPort *timerPort = CreateMsgPort(); struct timerequest *tr = (struct timerequest *) CreateIORequest(timerPort, sizeof(struct timerequest)); BYTE err = OpenDevice("timer.device", UNIT_MICROHZ, (struct IORequest *)tr, 0); if (err != 0) { /* handle error */ } /* IMPORTANT: after opening, you can get TimerBase for direct calls: */ struct Library *TimerBase = (struct Library *)tr->tr_node.io_Device; /* Now you can call AddTime(), SubTime(), CmpTime(), ReadEClock() */ ``` --- ## Simple Delay ```c /* Block the current task for exactly 2.5 seconds: */ tr->tr_node.io_Command = TR_ADDREQUEST; tr->tr_time.tv_secs = 2; tr->tr_time.tv_micro = 500000; /* 0.5 sec */ DoIO((struct IORequest *)tr); /* blocks until done */ ``` ### Non-Blocking Delay ```c /* Start delay, continue doing work, then wait: */ tr->tr_node.io_Command = TR_ADDREQUEST; tr->tr_time.tv_secs = 0; tr->tr_time.tv_micro = 100000; /* 100 ms */ SendIO((struct IORequest *)tr); /* non-blocking */ /* ... do other work ... */ /* Check if timer expired: */ if (CheckIO((struct IORequest *)tr)) { WaitIO((struct IORequest *)tr); /* collect result */ /* timer expired */ } ``` ### Signal-Based Waiting ```c /* Wait for timer OR user input: */ ULONG timerSig = 1L << timerPort->mp_SigBit; ULONG windowSig = 1L << window->UserPort->mp_SigBit; SendIO((struct IORequest *)tr); ULONG sigs = Wait(timerSig | windowSig); if (sigs & timerSig) { WaitIO((struct IORequest *)tr); /* handle timeout */ } if (sigs & windowSig) { AbortIO((struct IORequest *)tr); WaitIO((struct IORequest *)tr); /* handle window event */ } ``` --- ## Getting Current Time ```c /* Get system time (wall clock since midnight Jan 1, 1978): */ tr->tr_node.io_Command = TR_GETSYSTIME; DoIO((struct IORequest *)tr); Printf("Time: %lu.%06lu seconds since epoch\n", tr->tr_time.tv_secs, tr->tr_time.tv_micro); ``` ### Time Arithmetic ```c /* After opening timer.device and getting TimerBase: */ struct timeval t1, t2, diff; /* Measure elapsed time: */ tr->tr_node.io_Command = TR_GETSYSTIME; DoIO((struct IORequest *)tr); t1 = tr->tr_time; /* ... do work ... */ DoIO((struct IORequest *)tr); t2 = tr->tr_time; /* Compute difference: */ diff = t2; SubTime(&diff, &t1); Printf("Elapsed: %lu.%06lu s\n", diff.tv_secs, diff.tv_micro); /* Compare times: */ LONG cmp = CmpTime(&t1, &t2); /* <0: t10: t1>t2 */ ``` --- ## High-Resolution Timing (ReadEClock) ```c /* Most precise timing available — E-clock resolution: */ struct EClockVal start, end; ULONG efreq = ReadEClock(&start); /* returns ticks/second */ /* ... code to benchmark ... */ ReadEClock(&end); /* Compute elapsed microseconds: */ ULONG ticks = end.ev_lo - start.ev_lo; /* assumes <4 billion ticks */ ULONG usecs = ticks * 1000000 / efreq; Printf("Elapsed: %lu µs (E-clock freq: %lu Hz)\n", usecs, efreq); ``` | Standard | E-clock Freq | Tick Resolution | |---|---|---| | PAL | 709,379 Hz | ~1.410 µs | | NTSC | 715,909 Hz | ~1.397 µs | --- ## Periodic Timer (Game Loop / Audio Refill) ```c /* Classic pattern: periodic callback using timer.device */ #define FRAME_USEC 20000 /* 50 Hz (PAL frame rate) */ void GameLoop(void) { ULONG timerSig = 1L << timerPort->mp_SigBit; /* Kick off first timer request: */ tr->tr_node.io_Command = TR_ADDREQUEST; tr->tr_time.tv_secs = 0; tr->tr_time.tv_micro = FRAME_USEC; SendIO((struct IORequest *)tr); BOOL running = TRUE; while (running) { ULONG sigs = Wait(timerSig | SIGBREAKF_CTRL_C); if (sigs & timerSig) { WaitIO((struct IORequest *)tr); /* --- Game logic here --- */ UpdateGame(); RenderFrame(); /* Re-arm timer: */ tr->tr_time.tv_secs = 0; tr->tr_time.tv_micro = FRAME_USEC; SendIO((struct IORequest *)tr); } if (sigs & SIGBREAKF_CTRL_C) running = FALSE; } AbortIO((struct IORequest *)tr); WaitIO((struct IORequest *)tr); } ``` --- ## Common Pitfalls | Pitfall | Problem | Solution | |---|---|---| | Reusing active IORequest | Sending a `TR_ADDREQUEST` while previous is pending | Use two timerequest structs, or `WaitIO` first | | Forgetting `WaitIO` after `AbortIO` | Leaves IORequest in limbo — crash on next use | Always `WaitIO` after `AbortIO`, even if aborted | | Using `UNIT_VBLANK` for short delays | 20 ms granularity — actual delay is 0 to 20 ms | Use `UNIT_MICROHZ` for sub-20ms precision | | Not opening timer for `ReadEClock` | `TimerBase` is NULL — crash | Must `OpenDevice` first to get `TimerBase` | | Ignoring PAL/NTSC differences | Hardcoded periods wrong on other standard | Use `ReadEClock()` frequency for calculations | --- ## References - NDK39: `devices/timer.h` - ADCD 2.1: timer.device autodocs - HRM: CIA timer chapter - See also: [interrupts.md](../06_exec_os/interrupts.md) — VBlank interrupt chain