Only two things are needed for each timer: the duration, or interval, and the starting time. Since the need for processing delays can vary so widely, MIRTOS has five (5) types of timers with different resolutions and ranges:
Bytes | Timer Range | Resolution | System Function |
---|---|---|---|
1 | 1 to 250 | mSec | msTimerBYTE |
2 | 1 to 65,530 | mSec | msTimerWORD |
4 | 1 to 4,294,967,290 | mSec | msTimerULONG |
1 | 1 to 250 | Seconds | SecTimerBYTE |
2 | 1 to 65,530 | Seconds | SecTimerWORD |
Each of the timers return its results BOTH in Register R24 and in the ZERO flag.
The multitasking, level display, and pump control System Applications each use a single msTimerWORD timer. Each timer, with its duration WORD stored first in SRAM followed immediately by its starting time WORD, is contained within the Application instance's configuration.
The sequencer System Application uses msTimerWORD, the time to wait between calls to the update logic for that step, then SecTimerWORD, the maximum time to remain in that step. Only the durations of each need to be specified in the configuration, since the Operating System initializes each starting time WORD when it begins a new sequencing step.
The scripting logic has the ability to use any of the five (5) types of timers.
Subroutines can call any of the timers. It doesn't make much sense to call a timer subroutine if the called routine was invoked using a timer. There is one instance where embedding a timer call in a subroutine makes a lot of sense, however. It is when using the System Function which calls an assigned subroutine each pass through the main() loop. In this situcation, the subroutine is responsible for finding and assigning its own SRAM memory locations. These can certainly be in the uVars.B[##] range. Here is an example using BYTE timers:
WORD timers are just a little more complicated. In this example, the milliseconds and Seconds selection is made in the Assembly code instead as a configurable option during runtime:BYTETimer = 0x2FD ; Specify an SRAM location for 3 bytes IbDuration = 0 ; Index of the Duration byte at that address IbLastStart = 1 ; then indices of target and configuration IbOptions = 2 ; bytes which follow it InlineBYTETimer: LDI R31, BYTETimer >> 8 ; Point 'Z' at the first of the three (3) LDI R30, BYTETimer & 0xFF ; BYTE variables defined above LDD R24, Z + IbDuration ; Load both the interval and the time value LDD R22, Z + IbLastStart ; saved the last time the timer fired LDD R20, Z + IbOptions ; This lets us select which timer to use SBRS R20, 0 ; If the Low Order Bit is clear (logic 0), CALL AmsTimerBYTE ; then the millisecond timer is selected SBRC R20, 0 ; otherwise the Seconds timer is desired CALL ASecTimerBYTE ; All timers return status in both R24 and BREQ AIBT_Exit ; ZERO flag (TRUE = not done, FALSE = done) SBRS R20, 0 ; The timer has timed out; either get the CALL AmillisLowBYTE ; milliseconds or System Seconds Low Order SBRC R20, 0 ; byte, depending on which timer was being CALL ASystemSecondsLowBYTE ; used STD Z + IbLastStart, R22 ; Store the new starting time for next time CALL ATheTimedTask ; Call the subroutine to do the desired task IBT_Exit: RET
Finally, for completeness, here is code for an UNSIGNED LONG milliseconds timer. There is no ULONG System Seconds timer.WORDTimer = 0x2FC ; Specify 4 SRAM BYTEs: 0x2FC to 0x2FF IwDuration = 0 ; Index constants IwLastStart = 2 InlineWORDTimer: LDI R31, WORDTimer >> 8 ; Setup the 'Z' pointer LDI R30, WORDTimer & 0xFF LDD R24, Z + IwDuration ; Read the Low Order BYTE first then the LDD R25, Z + IwDuration + 1 ; High Order BYTE at the next higher SRAM LDD R22, Z + IwLastStart ; address into R25:R24 LDD R23, Z + IwLastStart + 1 ; Read the last start time into R23:R22 .IF SELECT_SECONDS == TRUE CALL AmsTimerWORD BREQ AIWT_Exit CALL AmillisLowWORD ; mSec Low WORD returned in R25:R24 .ELSE CALL ASecTimerWORD BREQ AIWT_Exit CALL ASystemSecondsLowWORD ; System Seconds LSWord in R25:R24 .ENDIF STD Z + IbLastStart , R24 ; Store the new starting time, in LSByte STD Z + IbLastStart + 1, R25 ; then MSByte order CALL ATheTimedTask ; Call the "workhorse" subroutine IWT_Exit: RET
Why have all these types of timers? Two somewhat conflicting aspects of Operating Systems are time efficiency and code efficiency. It is more efficient to check BYTEs instead of WORDs or ULONGs and similarly, it is more efficient to check WORDS rather than ULONGs. This is especially true when it is being done many hundreds or even thousands of times each second. It turns out that although there is a need for ULONG time periods, they really aren't needed that often.ULONGTimer = 0x2F8 ; Specify 8 SRAM BYTEs: 0x2F8 to 0x2FF IulDuration = 0 ; The index constants IulLastStart = 4 InlineULONGTimer: LDI R31, ULONGTimer >> 8 ; Setup the 'Z' pointer LDI R30, ULONGTimer & 0xFF LDD R22, Z + IulDuration LDD R23, Z + IulDuration + 1 ; Load the duration into R25:R24:R23:R22 LDD R24, Z + IulDuration + 2 LDD R25, Z + IulDuration + 3 LDD R18, Z + IulLastStart ; Load the last time into R21:R20:R19:R18 LDD R19, Z + IulLastStart + 1 LDD R20, Z + IulLastStart + 2 LDD R21, Z + IulLastStart + 3 CALL AmsTimerULONG BREQ AIULT_Exit CALL Amillis STD Z + IulLastStart , R22 ; Store the new starting time, in LSByte STD Z + IulLastStart + 1, R23 ; to MSByte order STD Z + IulLastStart + 2, R24 STD Z + IulLastStart + 3, R25 CALL ATheTimedTask ; Call the "workhorse" subroutine IULT_Exit: RET
Notes: | |
The interval between timer checks isn't really that important. It only matters if the time delay has passed or not. Since the main routine usually loops faster than once each millisecond, it executes over 1,000 timer checks each second. It takes less than 5 microseconds to check a timer when the MCU is being clocked at 8 MHz. | |
A time delay of zero is considered to ALWAYS be timed out, which means a subroutine can change its own timer to zero to cause it to time out (or "fire") again the very next time it is checked. |
See also: | |
The BYTE, WORD (2-BYTE), and ULONG (4-BYTE) timers. |