This code provides a timer system that allows many timers to
be defined, all slaved from a single periodic interrupt. The
Forth words in the user accessible group documented below are
compatible with the token definitions for the PRACTICAL virtual
machine and with the code supplied with MPE's embedded targets.
This code assumes the presence of TICKS
which returns a time value incremented in milliseconds.
The timebase is approximate, and granularity and jitter are affected by OS X and the time taken to run your own code. By default, the timer is set to run every 100ms. The source code is in the the file Lib/Lin32/TimeBase.fth. If you are using the multitasker, you must compile TimeBase.fth after MultiLin32.fth.
The timer chain is built using a buffer area, and two chain pointers. Each timer is linked into either the free timer chain, or into the active timer chain.
All time periods are in milliseconds. Note that on a 32 bit system such as VFX Forth, these time periods must be less than 2^31-1 milliseconds, say 596 hours or 24 days, whereas if the code is ported to a 16 bit system, time periods must be less than 2^15-1 milliseconds, say 32 seconds.
These basic words are defined for applications to use the
START-TIMERS \ -- ; must do this first
STOP-TIMERS \ -- ; closes timers
AFTER \ xt ms -- timerid/0 ; runs xt once after ms
EVERY \ xt ms -- timerid/0 ; runs xt every ms
TSTOP \ timerid -- ; stops the timer
MS \ period -- ; wait for ms
After the timers have been started, actions can be added. The example below starts a timer which puts a character on the debug console every two seconds.
start-timers
: t \ -- ; will run every 2 seconds
[char] * emit
;
' t 2 seconds every \ returns timerid, use TSTOP to stop it
The item on stack is a timer handle, use TSTOP
to halt
this timer.
AFTER
is useful for creating timeouts, such as is required
to determine if something has happened in time. AFTER
returns a timerid. If the action you are protecting happens
in time, just use TSTOP
when the action happens, and
the timer will never trigger. If the action does not happen,
the timer event will be triggered.
All timers are executed within a single callback, and so all
timer action words share a common user area. This has some
impact on timer action words. Since you do not know in which
order timer action words are executed, you must set up any
USER
variables such as BASE
that you may use,
either directly or indirectly.
The callback that handles all the timers sets IP-HANDLE
and OP-HANDLE
to a default that corresponds to the
interactive Forth console. If you use Forth I/O words such
as EMIT
and TYPE
within a timer action, you
must set IP-HANDLE
and OP-HANDLE
before
using the I/O. For the sake of other timer action routines
that may still be using default I/O, it is polite to save and
restore IP-HANDLE
and OP-HANDLE
in your timer
action words.
Do not worry about calling TSTOP
with a timerid that
has already been executed and removed from the active timer
chain; if TSTOP
cannot find the timer, it will ignore
the request.
Be sure to use START-TIMERS
in your main task, so that
the timer is not destroyed if a thread terminates.
The following discussion is relevant if you want to modify this code or port it to an embedded target. Functionally equivalent code is provided with MPE's Forth VFX cross compilers. In the OS X environment, timer interrupts are implemented by signals, callbacks and critical sections.
By default, the word DO-TIMERS
is run from within
the periodic timer callback. You may have latency issues if
a large number of timers is used, or if some timer routines
take a considerable time. In this case, it may be be better
to set up the timer routine to RESTART
a task which
calls DO-TIMERS
, e.g.
: TIMER-TASK \ --
<initialise>
BEGIN
DO-TIMERS STOP
AGAIN
;
Such a strategy also permits you to use a fast timer, say 1ms,
for a clock, and to trigger TIMER-TASK
every say 32 ms.
/semosx buffer: lpcs \ -- sem
Semaphore used for critical sections.
#32 constant #timers \ -- n ; maximum number of timers
A constant used at compile time to set the maximum number
of timers required. Each timer requires RAM as defined by
the ITIMER
structure below.
struct itimer \ -- len
Interval timer structure.
cell field itlink \ link to next timer ; MUST be first cell field itTimerId \ timer ID cell field itinterval \ period of timer in MS cell field ittimeout \ next timeout cell field itmode \ mode/flags, 0=periodic, 1=one shot cell field itxt \ word to execute end-struct
: after \ xt period -- timerid/0 ; xt is executed once,
Starts a timer that executes once after the given period.
A timer ID is returned if the timer could be started,
otherwise 0 is returned.
: every \ xt period -- timerid/0 ; periodically
Starts a timer that executes every given period. A timer
ID is returned if the timer could be started, otherwise 0
is returned. The returned timerID can be used by TSTOP
to stop the timer.
: tstop \ timerid --
Removes the given timer from the active list.
: seconds \ n -- n'
Converts seconds to milliseconds.
struct /itimerval \ -- len
Corresponds to the OS X itimerval structure.
int it.currsecs \ current period int it.currusecs \ in seconds & microseconds int it.nextsecs \ next period (0 to stop) int it.nextusecs \ in seconds & microseconds end-struct
/itimerval buffer: TBtimer \ -- addr
Structure controlling the main timer.
: SetTimerData \ ms timer --
Set a OS X itimerval structure to run every ms
milliseconds. Setting 0 ms will stop the timer.
#100 constant /period \ -- ms
Main timer period in milliseconds.
3 0 callback: SIGALRMhandler \ -- entrypoint
SIGALRM callback for main timer.
: doSIGALRM \ signum *siginfo *ucontext --
Action of main timer.
: start-timers \ -- ; Start internal time clock
Initialises the timebase system, and starts the timebase
system. Note that all timer actions are cleared.
Performed at start up.
: Stop-Timers \ -- ; disable timer system
Halts the timebase interrupt.