|
1 | 1 | # Software Timers
|
2 | 2 |
|
3 |
| -An implementation of lightweight software timeouts and periodic timers. |
| 3 | +This module provides polling based software timers for executing code after a |
| 4 | +delay or periodically in millisecond resolution via `modm::Clock` and in |
| 5 | +microsecond resolution via `modm::PreciseClock`. |
4 | 6 |
|
5 |
| -- `modm::Timestamp` containing a time in millisecond resolution up to 49 days. |
6 |
| -- `modm::Timeout` for timeouts up to 24 days. |
7 |
| -- `modm::PeriodicTimer` for periodic timeouts up to 24 days periods. |
| 7 | +To delay or delegate execution to the future, you can use `modm::Timeout` to set |
| 8 | +a duration after which the timeout expires and executes your code: |
8 | 9 |
|
9 |
| -There are also 16-bit versions of these, in case you need to preserve memory: |
10 |
| - |
11 |
| -- `modm::ShortTimestamp` containing a time in millisecond resolution up to 65 seconds. |
12 |
| -- `modm::ShortTimeout` for timeouts up to 32 seconds. |
13 |
| -- `modm::ShortPeriodicTimer` for periodic timeouts up to 32 second periods. |
14 |
| - |
15 |
| -These classes default to using `modm::Clock`, which must be implemented on your |
16 |
| -system to return a time with millisecond resolution. |
17 |
| - |
18 |
| -You may also create your own timers with custom time bases and resolutions |
19 |
| -using these classes: |
| 10 | +```cpp |
| 11 | +modm::Timeout timeout{100ms}; |
| 12 | +while (not timeout.isExpired()) ; |
| 13 | +// your code after a delay |
| 14 | +``` |
20 | 15 |
|
21 |
| -- `modm::GenericTimestamp`. |
22 |
| -- `modm::GenericTimeout`. |
23 |
| -- `modm::GenericPeriodicTimer`. |
| 16 | +However, this construct is not very useful, particularly since you could also |
| 17 | +simply use `modm::delay(100ms)` for this, so instead use the `execute()` |
| 18 | +method to poll non-blockingly for expiration: |
24 | 19 |
|
25 |
| -!!! warning |
26 |
| - Never use these classes when a precise timebase is needed! |
| 20 | +```cpp |
| 21 | +modm::Timeout timeout{100ms}; |
27 | 22 |
|
28 |
| -## Timeouts |
| 23 | +void update() |
| 24 | +{ |
| 25 | + if (timeout.execute()) |
| 26 | + { |
| 27 | + // your code after a expiration |
| 28 | + } |
| 29 | +} |
| 30 | +// You must call the update() function in your main loop now! |
| 31 | +int main() |
| 32 | +{ |
| 33 | + while(1) |
| 34 | + { |
| 35 | + update(); |
| 36 | + } |
| 37 | +} |
| 38 | +``` |
29 | 39 |
|
30 |
| -The `modm::GenericTimeout` classes allow for a signal to be generated after a |
31 |
| -period of time, which can also be used to execute code once after timeout |
32 |
| -expiration. |
| 40 | +The `execute()` method returns true only once after expiration, so it can be |
| 41 | +continuously polled somewhere in your code. A more comfortable use-case is to |
| 42 | +use a `modm::Timeout` inside a class that needs to provide some asynchronous |
| 43 | +method for timekeeping: |
33 | 44 |
|
34 |
| -Its behavior can be described by the following annotated waveform: |
| 45 | +```cpp |
| 46 | +class DelayEvents |
| 47 | +{ |
| 48 | + modm::Timeout timeout; |
| 49 | +public: |
| 50 | + void event() { timeout.restart(100ms); } |
| 51 | + void update() |
| 52 | + { |
| 53 | + if (timeout.execute()) { |
| 54 | + // delegated code here |
| 55 | + } |
| 56 | + } |
| 57 | +} |
| 58 | +``` |
35 | 59 |
|
36 |
| -- C: Default Constructor |
37 |
| -- S: (Re-)Start timeout |
38 |
| -- E: Timeout Expired |
39 |
| -- H: Code handler (`execute()` returned `true`) |
40 |
| -- P: Stop timeout |
| 60 | +However, for more complex use-cases, these classes are intended to be used with |
| 61 | +Protothreads (from module `modm:processing:protothread`) or Resumable Functions |
| 62 | +(from module `modm:processing:resumable`) to implement non-blocking delays. |
41 | 63 |
|
| 64 | +```cpp |
| 65 | +class FancyDelayEvents : public modm::pt::Protothread |
| 66 | +{ |
| 67 | + modm::Timeout timeout; |
| 68 | +public: |
| 69 | + void event() |
| 70 | + { |
| 71 | + this->restart(); // restart entire protothread |
| 72 | + } |
| 73 | + bool update() |
| 74 | + { |
| 75 | + PT_BEGIN(); |
| 76 | +
|
| 77 | + // pre-delay computation |
| 78 | + timeout.restart(100ms); |
| 79 | + PT_WAIT_UNTIL(timeout.isExpired()); |
| 80 | + // post-delay computation |
| 81 | +
|
| 82 | + PT_END(); |
| 83 | + } |
| 84 | +} |
42 | 85 | ```
|
43 |
| -Event: C S E H P S E H |
44 |
| - _____________ ______________ |
45 |
| -Expired: ______________/ \_______________________/ ... |
46 |
| - ______ __________ |
47 |
| -Armed: _______/ \__________________________/ \______________... |
48 |
| - _______ ____________ |
49 |
| -Stopped: \____________________/ \_________________________... |
50 | 86 |
|
51 |
| - _ _ |
52 |
| -Handle: ___________________/ \_______________________________/ \___________... |
| 87 | +For periodic timeouts, you could simply restart the timeout, however, the |
| 88 | +`restart()` method schedules a timeout from the *current* time onwards: |
53 | 89 |
|
54 |
| -Remaining: 0 | + | - | 0 | + | - |
| 90 | +```cpp |
| 91 | +void update() |
| 92 | +{ |
| 93 | + if (timeout.execute()) |
| 94 | + { |
| 95 | + // delayed code |
| 96 | + timeout.restart(); // restarts but with *current* time!!! |
| 97 | + } |
| 98 | +} |
55 | 99 |
|
56 |
| - time ——————> |
57 | 100 | ```
|
58 | 101 |
|
59 |
| -The default constructor initializes the timeout in the `Stopped` state, |
60 |
| -in which `isExpired()` and `execute()` never return `true`. |
61 |
| -If you need a timeout to expire immidiately after construction, you need |
62 |
| -to explicitly initialize the constructor with time `0`, which has the |
63 |
| -same behavior as `restart(0)`. |
64 |
| - |
65 |
| -If you want to execute code once after the timeout expired, poll the |
66 |
| -`execute()` method, which returns `true` exactly once after expiration. |
| 102 | +This can lead to longer period than required, particularly in a system that has |
| 103 | +a lot to do and cannot service every timeout immediately. The solution is to use |
| 104 | +a `modm::PeriodicTimer`, which only reimplements the `execute()` method to |
| 105 | +automatically restart the timer, by adding the interval to the *old* time, thus |
| 106 | +keeping the period accurate: |
67 | 107 |
|
68 | 108 | ```cpp
|
69 |
| -if (timeout.execute()) |
| 109 | +modm::PeriodicTimer timer{100ms}; |
| 110 | +void update() |
70 | 111 | {
|
71 |
| - // called once after timeout |
72 |
| - Led::toggle(); |
| 112 | + if (timer.execute()) // automatically restarts |
| 113 | + { |
| 114 | + // blink an LED or something |
| 115 | + } |
73 | 116 | }
|
74 | 117 | ```
|
75 | 118 |
|
76 |
| -Be aware, however, that since this method is polled, it cannot execute |
77 |
| -exactly at the time of expiration, but some time after expiration, as |
78 |
| -indicated in the above waveform graph. |
79 |
| - |
80 |
| -The `remaining()` time until expiration is signed positive before, and |
81 |
| -negative after expiration. This means `Clock::now() + Timeout::remaining()` |
82 |
| -will yield the timestamp of the expiration. |
83 |
| -If the timeout is stopped, `remaining()` returns zero. |
| 119 | +The `execute()` method actually returns the number of missed periods, so that in |
| 120 | +a heavily congested system you do not need to keep track of time yourself. This |
| 121 | +can be particularly useful when dealing with soft-time physical systems like LED |
| 122 | +animations or control loops: |
84 | 123 |
|
| 124 | +```cpp |
| 125 | +modm::PeriodicTimer timer{1ms}; // render at 1kHz ideally |
| 126 | +void update() |
| 127 | +{ |
| 128 | + // call only once regarless of the number of periods |
| 129 | + if (const size_t periods = timer.execute(); periods) |
| 130 | + { |
| 131 | + animation.step(periods); // still compute the missing steps |
| 132 | + animation.render(); // but only render once please |
| 133 | + } |
| 134 | + // or alternatively to call the code the number of missed periods |
| 135 | + for (auto periods{timer.execute()}; periods; periods--) |
| 136 | + { |
| 137 | + // periods is decreasing! |
| 138 | + } |
| 139 | + // This is fine, since execute() is evaluated only once! |
| 140 | + for (auto periods : modm::range(timer.execute())) |
| 141 | + { |
| 142 | + // periods is increasing! |
| 143 | + } |
| 144 | + // THIS WILL NOT WORK, since execute() reschedules itself immediately! |
| 145 | + for (auto periods{0}; periods < timer.execute(); periods++) |
| 146 | + { |
| 147 | + // called at most only once!!! periods == 0 always! |
| 148 | + } |
| 149 | +} |
| 150 | +``` |
85 | 151 |
|
86 |
| -## Periodic Timers |
87 |
| - |
88 |
| -The `modm::GenericPeriodicTimer` class allows for periodic code execution. |
89 |
| - |
90 |
| -Its behavior can be described by the following annotated waveform: |
| 152 | +!!! warning "DO NOT use for hard real time systems!" |
| 153 | + You are responsible for polling these timers `execute()` methods as often as |
| 154 | + required. If you need to meed hard real time deadlines these are not the |
| 155 | + timers you are looking for! |
91 | 156 |
|
92 |
| -- C: Constructor |
93 |
| -- S: (Re-)Start timer |
94 |
| -- I: Period interval |
95 |
| -- H: Code handler (`execute()` returned `true`) |
96 |
| -- P: Stop timer |
| 157 | +!!! note "Timers are stopped by default!" |
| 158 | + If you want to start a timer at construction time, give the constructor a |
| 159 | + duration. Duration Zero will expire the timer immediately |
97 | 160 |
|
98 |
| -``` |
99 |
| -Event: C IH I I H I S IH I IH P |
100 |
| - _ _____________ __ _ ______ |
101 |
| -Expired: __________/ \_______/ \_____/ \____/ \__/ \____... |
102 |
| - __________ _______ _____ ____ __ _ |
103 |
| -Armed: \_/ \_____________/ \__/ \_/ \______/ \__... |
104 |
| - __ |
105 |
| -Stopped: ______________________________________________________________/ ... |
| 161 | +## Resolution |
106 | 162 |
|
107 |
| - _ _ _ _ |
108 |
| -Handle: __________/ \___________________/ \_____________/ \_______/ \____... |
| 163 | +Two timer resolutions are available, using `modm::Clock` for milliseconds and |
| 164 | +`modm::PreciseClock` for microseconds. They follow the same naming scheme: |
109 | 165 |
|
110 |
| -Remaining: + |0| + | - |0| + | -| + |0| +| - |0|+| 0 |
| 166 | +- `modm::Timeout`, `modm::PeriodicTimer`: 49 days in milliseconds and 8 bytes. |
| 167 | +- `modm::PreciseTimeout`, `modm::PrecisePeriodicTimer`: 71 mins in microseconds |
| 168 | + and 8 bytes. |
111 | 169 |
|
112 |
| - time ——————> |
113 |
| -``` |
| 170 | +If you deal with short time periods, you can save a little memory by using the |
| 171 | +16-bit versions of the same timers: |
114 | 172 |
|
115 |
| -If you want to execute code once per period interval, poll the |
116 |
| -`execute()` method, which returns `true` exactly once after expiration. |
| 173 | +- `modm::ShortTimeout`, `modm::ShortPeriodicTimer`: 65 seconds in milliseconds |
| 174 | + and 4 bytes. |
| 175 | +- `modm::ShortPreciseTimeout`, `modm::ShortPrecisePeriodicTimer`: 65 |
| 176 | + milliseconds in microseconds and 4 bytes. |
117 | 177 |
|
118 |
| -```cpp |
119 |
| -if (timer.execute()) |
120 |
| -{ |
121 |
| - // periodically called once |
122 |
| - Led::toggle(); |
123 |
| -} |
124 |
| -``` |
125 | 178 |
|
126 |
| -Be aware, however, that since this method is polled, it cannot execute |
127 |
| -exactly at the time of expiration, but some time after expiration, as |
128 |
| -indicated in the above waveform graph. |
129 |
| -If one or several periods are missed when polling `execute()`, these |
130 |
| -code executions are discarded and will not be caught up. |
131 |
| -Instead, `execute()` returns `true` once and then reschedules itself |
132 |
| -for the next period, without any period skewing. |
133 |
| - |
134 |
| -Notice, that the `PeriodicTimerState::Expired` is reset to |
135 |
| -`PeriodicTimerState::Armed` only after `execute()` has returned `true`. |
136 |
| -This is different to the behavior of GenericTimeout, where calls to |
137 |
| -`GenericTimeout::execute()` have no impact on class state. |
138 |
| - |
139 |
| -The `remaining()` time until period expiration is signed positive before, |
140 |
| -and negative after period expiration until `execute()` is called. |
141 |
| -If the timer is stopped, `remaining()` returns zero. |
|
0 commit comments