A common requirement is for sections of code to be executed to a schedule or after a period of time. The following uses an array of flags and counters to keep track of (in this case but easily tweak-able) 10 timers, with option for them to be single-shot or repeating. The timers can be stopped or temporarily disabled. To check if a period has elapsed, simply check the flag and perform your desired action if it is set (you need to reset it as part of this). This is a key requirement for
State Machine Methodology.The Subs/Functions are inspired by the Locomotive Basic commands of the same names and make executing sections of code asynchronously a breeze. MMBasic provides four timers but they expect a sub program to be called each time and they have to be handled (i.e. if they are one-shot) by the called sub. The following code uses a single MMBasic timer but then expands this to practically unlimited timers and really easy to set one running. The main loop simply checks for the relevant flag and takes action (calls a Sub, changes the state of an output pin etc...) if it is set.
After Set a semaphore after a period of time. E.g. After 30 seconds
Every Set a semaphore at the same period repeatedly. E.g. Every 10 seconds
At Set a semaphore at a specific DateTime. E.g. At 09/09/2019 05:25:30
Remain Return the remaining time (i.e. before it was due to set the semaphore) and optionally stop the timer.
DI & EI Temporarily Disable or Enable all timers.
Pros:- Easy to configure a one-shot timer.
- Easy to re-use timers for different sections of code.
- Retrieve the remaining time on a timer and optionally stop it.
- No need to worry about handling SETTICK - No dedicated Sub to handle an event - just do it inline when it is convenient.
- Gives much greater scope for the number of timers.
- Multiple ticks from the same counter give only a single event - No event "storms" for slow-running code.
- Manageable program interruption - It is totally down to the program flow - nothing kicking off "behind your back".
- No implicit priority to timers, it is down to how the flags get checked.
Cons:- Timer resolution is not as good as SETTICK on slow systems - very small delays are likely to be inaccurate, e.g. waiting one second when the service period is 1000mS.
- As the internal timer is used to determine the time periods, very long periods may be inaccurate due to clock drift (see clocktrim in the MicroMite manual). Note this is no worse than the SETTICK command. This may become particularly noticeable when using At() - A little work could provide a better solution if it becomes critical.
The following provides a core timer handler and three Subs to set repetitive or one-shot timers and a method to stop a timer (and return the remaining count before it was stopped).
Dependencies:- Bit Manipulation Functions Bits 0 - 9 in the Flags field correspond to timers 0-9... tweak as necessary. Bit 10 is the global timer disable bit (=1). In the disabled state, timers are not decremented and any time spent disabled will be effectively added to running timers. E.g. if a timer is due to expire after 10 seconds but spends five seconds disabled, the total delay will be 15 seconds. DI/EI should be used to stop the timers when some critical process is taking place to prevent state changes.
- Three global variables are required, two integer arrays and a simple integer to hold the flags.
- UnixTime required if you intend to use the At functionality
Variable Descriptions:TMRctr() maintains the count for a given timer
TMRini()holds the timer type and its initialization value.
- bits 0-61 store the initial value
- bits 63&62 store the format of the counter:
0=disabled
1=(actually 0x4000000000000000) indicates a one shot "AFTER x" type counter
2=(actually 0x8000000000000000) indicates a repetitive "EVERY x" type counter
As written below, it provides 10 timers which should be loads for just about any use, but this can be tweaked as you see fit.
The CoreTMR needs to be set running with the SETTICK command - timer counter values are a multiple of the interval used here. Try not to be too aggressive with small/fast intervals but the larger the interval the greater the loss of resolution. Find something that works for you but remember to adjust your timer values accordingly e.g. if you want ticks 10 times a second, the setitick must be set to 100 - and the values used in AFTER & EVERY would need to reflect the increase in resolution -in the above example 1 second becomes 10 counts etc.
Example:
Preamble:
Option Base 0
Dim Integer TMct(9),TMin(9),Flag
SETTICK 1000,CoreTMR,4' service timers every second
Main:
' example usage for the main loop
' gives the user 10 seconds to hit a key (using timer 1)
Print "Quick Press a key!"
After 10,1 ' 10 seconds using timer 1
Every 1 ,2 ' count 1 second intervals using timer 2
Every 2 ,3 ' count 2 second intervals using timer 3
Do
a$=Inkey$
If FlagTest(2) Then FlagRes 2:Print "x"' output an x while we wait
If FlagTest(3) Then FlagRes 3:Print "y"' output a y while we wait
Loop Until FlagTest(1) or a$<>""
x=Remain(1)' stop the timer and get the remaining time
If FlagTest(1) Then ' the timer expired?
Print "Too late!"
Else ' otherwise someone pressed a key
Print "that was close. There was only ";x;" seconds left!"
EndIf
DI
For n=1 To 3: x=Remain(n): Next
EI
End
' The subs
Sub CoreTMR ' needs to run on a ticker at whatever interval is required - this is also the multiple of timer counts
If FlagTest(10) Then Exit Sub
Local Integer n
For n=0 To 9
Select Case TMin(n)>>62 ' extract the top 2 bits
Case 0,3 'disabled or invalid
FlagRes(n)
Case 1' AFTER
If TMct(n) then
TMct(n)=TMct(n)-1
If TMct(n)=0 Then FlagSet(n) ' indicate the timer has expired
EndIf
Case 2' EVERY
If TMct(n) then
TMct(n)=TMct(n)-1
If TMct(n)=0 Then
FlagSet(n) ' indicate the timer has expired
TMct(n)=TMin(n) And &h3FFFFFFFFFFFFFFF ' reset the timer
EndIf
EndIf
End Select
Next
End Sub
Sub After(Interval As Integer,Tmr As Integer)' starts a one-shot timer
TMin(Tmr)=Interval OR &h4000000000000000:TMct(Tmr)=Interval And &h3FFFFFFFFFFFFFFF:FlagRes(Tmr)
End Sub
Sub Every(Interval As Integer,Tmr As Integer)' starts a repetitive timer
TMin(Tmr)=Interval OR &h8000000000000000:TMct(Tmr)=Interval And &h3FFFFFFFFFFFFFFF:FlagRes(Tmr)
End Sub
Function Remain(Tmr As Integer, opt As Integer) As Integer' returns the remaining time in the counter.
'if opt=0 (or omitted) timer is stopped
'if opt<>1 timer remains running
Remain=TMct(Tmr)
If opt=0 Then TMin(Tmr)=TMin(Tmr) And &h3FFFFFFFFFFFFFFF
End Function
Sub DI
FlagSet 10
End Sub
Sub EI
FlagRes 10
End Sub
All
the functions are inspired by some innovative (for the time) namesakes in Locomotive basic as used on the Amstrad CPC of the 1980s.