amiga-bootcamp/10_devices/timer.md
Ilia Sharin da9e7d3b63 Phase 1: enrich 07_dos and 10_devices (highest FPGA priority)
07_dos:
- file_io.md: 108→240+ lines — buffered I/O (FRead/FWrite/SetVBuf),
  access mode comparison, FileHandle struct with offsets, standard
  handles, Printf %ld warning, FileInfoBlock, practical patterns
  (copy file, get size, load to RAM), error code table
- filesystem.md: 91→270+ lines — full disk geometry (ADF/HDF),
  all 8 DOS\x filesystem IDs, root block byte-level layout, file
  header layout with reverse-order pointer quirk, OFS vs FFS data
  blocks with efficiency numbers, bitmap blocks, extension blocks,
  checksum algorithm, Python ADF reader
- locks_examine.md: 113→270+ lines — lock semantics diagram, FileLock
  struct with handler discovery, ExAll bulk scan, practical patterns
  (atomic write, path resolution, volume info), 4 antipatterns
  (leaked locks, exclusive too long, unchecked IoErr, DupLock),
  pattern matching

10_devices:
- audio.md: 73→240+ lines — hardware architecture diagram, channel
  registers with offsets, period/frequency table, priority allocation,
  double-buffering, audio interrupts, AM/PM modulation, direct HW
- timer.md: 80→230+ lines — CIA timer hardware, all 5 units with
  decision flowchart, non-blocking delays, signal-based waiting,
  time arithmetic, ReadEClock, periodic game loop pattern, pitfalls
- trackdisk.md: 82→210+ lines — MFM encoding, track format, disk
  geometry, read/write/motor, change notification, track caching,
  direct hardware access, FPGA timing implications
- keyboard.md: 58→220+ lines — CIA-A serial handshake protocol with
  sequence diagram, bit rotation quirk, complete key code map,
  key matrix bitmap, reset sequence, FPGA notes
2026-04-23 20:23:50 -04:00

288 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[← 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<br/>or UNIT_ECLOCK"]
LONG --> VBLANK["UNIT_VBLANK<br/>(lower CPU overhead)"]
Q --> MEASURE["Need to measure<br/>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 (0999999) */
};
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: t1<t2, 0: equal, >0: 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