Multitasking: Timer tricks

Most control programs use time in some way or other. One technique I use, in association with the multitasking methods I've described, is "global timer ticks".

With the "main loop calling subroutines" structure, each task gets "run" exactly once for each time around the main loop. The timer ticks method exploits this. What I do is to insert into the loop a new task that uses a timer to set a semaphore for exactly one "lap" of the loop each time interval. Here is a basic example, with the time code embedded in the main loop:

MainLoop:
          ClrS       Tick100       ;Clear the 100mS timer tick
          Test       7             ;Has another 100mS expired?
          GoIfT      NoTick100     ;G/ not yet
          SetS       Tick100       ;Yes. Set the tick
          SetTimer   7,10          ;Restart the timer
NoTick100:
          GoSub      Task_1
          GoSub      Task_2
          GoSub      Task_3
... etc ..
          GoSub      Task_n

          GoTo       MainLoop

Now, every 100mS the Tick100 semaphore will be set for exactly one call to each of the Task_ subroutines. Any one of them that is interested can use the semaphore for whatever purpose it wants to. By counting ticks it can generate longer intervals.

In the following complete program I've made only one task, for illustration. It flashes a light on for 200mS and off for 800mS.

;The first 8 bytes of RAM I elect to reserve for semaphores
Tick100   sEQU       0

;I start "ordinary" RAM at 19 (after PermStore area)
FL_SuspAddr mEQU     19             ;2 bytes for suspend address
FL_Count    mEQU     21             ;Tick counter for the flasher

;I/O definition(s)
FlshO     oEQU       15             ;An MMi99 front panel LED

**************************
;initialization
          GoSub      FL_Init

****************************
MainLoop:
          ClrS       Tick100       ;Clear the 100mS timer tick
          Test       7             ;Has another 100mS expired?
          GoIfT      NoTick100     ;G/ not yet (still timing)
          SetS       Tick100       ;Yes. Set the tick
          SetTimer   7,10          ;Restart the timer
NoTick100:
          GoSub      FL_Update
          GoTo       MainLoop

******** Flasher *************************
; Flasher task
; Short name: FL_

;Function: Flash output FlshO 200mS on, 800mS off

;Methods:
;         FL_Init
;         FL_Update

;Uses:    Tick100 timer tick

FL_Init
          SetMem      FL_Count,1        ;initialize the counter to a "safe" value
FL_Susp1
          Suspend     FL_SuspAddr
          DecMGoIfNZ  FL_Count,FL_Susp1 ;Count the tick, test if counted to 0
          On          FlshO
          SetMem      FL_Count,2        ;Set counter for 200mS
FL_Susp2
          Suspend     FL_SuspAddr
          DecMRetIfNZ FL_Count          ;Count the tick, test if counted to 0
          Off         FlshO
          SetMem      FL_Count,8        ;Set counter for 800mS
          GoTo        FL_Susp1
FL_UpDate
          RetIfSF     Tick100           ;Do nothing if it's not tick time
          Resume      FL_SuspAddr

There are a few little tricks in this code:

  1. The timer tick is checked at FL_Update, before the Resume. In effect that means the whole task (apart from checking the tick) will only run on each timer tick (every 100mS). This can save a fair bit of code, providing you have a task that only needs to ever run synchronously with a tick.
  2. I initialized the counter FL_Count to 1. RAM is automatically cleared to zero, so without this initialization, the light would initially not come on for 25.5s (think about that one, try it!)
  3. In the FL_Susp1 segment I used a DecMGoIfNZ. In the FL_Susp2 segment I used a different count and test instruction. Study and compare the two instructions, and work out why and how the second one works in this program

In many of my programs I generate several ticks at different rates, typically 10mS, 100mS, 1S, 10S and 1min. I then use the fastest one possible for each interval I need to generate. For example, for a 1 minute interval I will count off 60 1S ticks, giving an accuracy of about 1S. On the question of accuracy, this technique will produce an error related to the latency between a timer expiring and the program "coming around the loop" and testing it. A similar scheme based on GetTick10 and/or GetTick100 (introduced in dialect 12) will not have cumulative errors. The main loop with tick generation then becomes:

MainLoop:
          GetTick100		;non-zero if there are any "unused" ticks.
          StoreS     Tick100
          GoSub      FL_Update
          GoTo       MainLoop

Not only is this more compact, it will be more accurate.

If you are using a board with a dialect earlier than 12, you can't use semaphores. However, you can still use timer ticks by using a 1-byte flag rather than a 1-bit semaphore. You would also have to use Branch/Target rather than Suspend/Resume to code the flasher task.

Note: The SuperTimer mechanism introduced in dialect 16 makes the above concept largely obsolete. It is still "valid", but SuperTimers are much easier to use.