mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-13 00:26:28 +00:00
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
288 lines
7.4 KiB
Markdown
288 lines
7.4 KiB
Markdown
[← 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 (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: 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
|