Periodic Timers

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.

The basics of timers

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.

Considerations when using timers

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.

Implementation issues

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.

Timebase glossary

/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.