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:
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.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 programIn 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.