mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-13 00:26:28 +00:00
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
This commit is contained in:
parent
aeaea88d75
commit
da9e7d3b63
7 changed files with 1599 additions and 301 deletions
|
|
@ -1,34 +1,110 @@
|
|||
[← Home](../README.md) · [Devices](README.md)
|
||||
|
||||
# timer.device — Timing and Delays
|
||||
# timer.device — Timing, Delays, and High-Resolution Timestamps
|
||||
|
||||
## Overview
|
||||
|
||||
`timer.device` provides precise timing services: delays, time-of-day, and high-resolution timestamps. It has two units:
|
||||
|
||||
| Unit | Constant | Resolution | Use |
|
||||
|---|---|---|---|
|
||||
| 0 | `UNIT_MICROHZ` | ~2µs (E-clock) | Short, precise delays |
|
||||
| 1 | `UNIT_VBLANK` | ~20ms (VBlank) | Long delays, lower overhead |
|
||||
| 2 | `UNIT_ECLOCK` | CIA E-clock ticks | Highest resolution timing (OS 2.0+) |
|
||||
| 3 | `UNIT_WAITUNTIL` | absolute time | Wait until specific time (OS 2.0+) |
|
||||
| 4 | `UNIT_WAITECLOCK` | E-clock absolute | (OS 2.0+) |
|
||||
`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).
|
||||
|
||||
---
|
||||
|
||||
## struct timeval / timerequest
|
||||
## 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 */
|
||||
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() */
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -36,17 +112,51 @@ struct timerequest {
|
|||
## Simple Delay
|
||||
|
||||
```c
|
||||
struct timerequest *tr = (struct timerequest *)
|
||||
CreateIORequest(port, sizeof(struct timerequest));
|
||||
OpenDevice("timer.device", UNIT_VBLANK, (struct IORequest *)tr, 0);
|
||||
|
||||
/* 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 = 0;
|
||||
DoIO((struct IORequest *)tr); /* blocks for 2 seconds */
|
||||
tr->tr_time.tv_micro = 500000; /* 0.5 sec */
|
||||
DoIO((struct IORequest *)tr); /* blocks until done */
|
||||
```
|
||||
|
||||
CloseDevice((struct IORequest *)tr);
|
||||
DeleteIORequest((struct IORequest *)tr);
|
||||
### 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 */
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -54,26 +164,125 @@ DeleteIORequest((struct IORequest *)tr);
|
|||
## 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\n", tr->tr_time.tv_secs, tr->tr_time.tv_micro);
|
||||
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
|
||||
## High-Resolution Timing (ReadEClock)
|
||||
|
||||
```c
|
||||
/* Read E-clock (OS 2.0+): */
|
||||
struct EClockVal eclock;
|
||||
ULONG freq = ReadEClock(&eclock); /* returns ticks/second */
|
||||
/* eclock.ev_hi, eclock.ev_lo = 64-bit tick count */
|
||||
/* Typical freq: 709379 Hz (PAL) or 715909 Hz (NTSC) */
|
||||
/* 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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue