C64 OS PROGRAMMER'S GUIDE
Timers are useful for many tasks, both high-level end-user features like a stopwatch or timer utility, and for low-level features such as blinking a button or menu option when it is triggered by a keyboard shortcut, or determining if a double-click has occurred.
C64 OS supports 5 simultaneously active timers. Timer countdowns are 24-bits wide with 1/60th of a second accuracy. Without additional effort, a countdown can be configured to last ~77.67 hours, or ~3.23 days. Timers support one-shot and continuous firing, can be paused, reset, canceled, and resized and redirected while queued.
RegPtr → Pointer to a timer structure
C ← Set on error, timer queue full
Enqueues the timer structure, and activates it.
No input or output parameters.
Counts down all enqueued, unpaused timers.
Called automatically by the system's IRQ service routine.
No input or output parameters.
Runs expired timer triggers.
Called automatically by the main event loop.
A timer consists of a 9-byte structure, and may be statically or dynamically allocated. A timer structure may reside behind the KERNAL ROM without issue. This allows for Utilities, which load load to behind the KERNAL ROM, to statically allocate a timer.
For a deep dive on how timers are implemented, how they work and how to use them, see the weblog post, Timers, One More Time.
To use a timer, allocate 9 bytes for the timer structure. The first three bytes, ttime, are the countdown time, least significant byte first, measured in Jiffies, or, 60ths of a second.
For example, to have a timer last 5 minutes, 5 * 60 = 300 seconds. 300 * 60 = 18,000 Jiffies. For byte values:
$50, $46, $00 $50 = 80 = 80 Jiffies $46 = 70 * 256 = 17920 Jiffies $00 = 0 * 256 * 256 = 0 Jiffies ------------------------------------ Total: 18000 Jiffies, or 5 minutes
The last the bytes, tvalu, are a reset time in the same format as ttime. If the timer is one-shot only, the tvalu reset time is not required, and does not need to be allocated. A one-shot timer structure then may consist of just 6 bytes.
The one byte following ttime, tstat, holds the current status flags of the timer. See Timer Status Flags below. The status flags are manipulated by the timedwn and timeevts routines, but they can also be programmatically changed, at any time, even when the timer is already queued. Note: Interrupts must be masked while changing tstat.
The two bytes following tstat, ttrig, is a 2-byte pointer to a routine that will be called, or triggered, by timeevts when the timer expires. This pointer can also be changed at any time even when the timer is queued. It is not necessary to mask interrupts when changing ttrig, because interrupts only affect tstat.
The following table lists the significance of the bits in the tstat byte of a timer structure.
|pause||tpause||7||If set, pauses a queued timer, preventing its countdown counting down.|
|interval||tintrvl||6||If set, a timer's ttime will automatically be reset from tvalu when it expires,
and then continue counting down.
If clear, an expired timer will be dequeued automatically after being triggered.
|cancel||tcancel||5||If set, a timer will be automatically dequeued and it will not be triggered.|
|reset||treset||4||If set, a timer's ttime will be reset from tvalu immediately, and then continue counting down.|
|expired||texprd||3||Is set by timedwn when a timer's ttime reaches zero. If tintrvl is set, ttime is
reset to tvalu.
If set, the timer will be triggered, ttrig will be called, by the main event loop. After being triggered, texprd is automatically unset.
|current||tcurrent||1 and 0||Captures current screen layer for redraw purposes.|
The easiest most straightforward way to use a timer is to statically allocate 9 bytes. With the ttime, tstat, ttrig, tvalu hardcoded. The default value for tstat is 0. Unpaused and one-shot. Not cancelled, not in need of reset and not expired. Point the ttrig at a routine to be called. To queue the timer, load a pointer to the structure into X and Y and call timeque. It immediately begins counting down, and when the time elapses, the trigger routine will be called.
The timers.s is not specifically required in this example. It defines the constants that would be used if you wanted to manipulate the structure, or change the status flags.
Including io.s brings in constants for the C64's IO addresses, this is used to refer to the VIC-II with a constant instead of just its address.
There is some application framework code that has been excluded for brevity and focus on timers. The init shows how to enqueue the timer. The timer structure appears below the init routine, and its bytes are statically allocated. The final three bytes, the reset value, is not strictly necessary if this timer is only going to be used once. The trigger points to the borderchng routine simply by using its label.
The borderchng routine increment's the border color by changing the border color register of the VIC-II.
Lastly, assuming this code is part of an application, is the externs table, used to bring in the timers module's header, and make a jumptable entry for timeque.
That's it. 10 seconds after the init code runs, the VIC-II's border color will increment. And then the one-shot timer will be over and will be dequeued.
This document is subject to revision updates.
Last modified: Sep 20, 2022