Skip to content

Commit 0809dcf

Browse files
committed
[timer] Adapt for modm::chrono clocks
1 parent da9a39e commit 0809dcf

File tree

9 files changed

+379
-484
lines changed

9 files changed

+379
-484
lines changed

src/modm/processing/protothread/module.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ public:
5151

5252
while (true)
5353
{
54-
timeout.restart(100);
54+
timeout.restart(100ms);
5555
Led::set();
5656
PT_WAIT_UNTIL(timeout.isExpired());
5757

58-
timeout.restart(200);
58+
timeout.restart(200ms);
5959
Led::reset();
6060
PT_WAIT_UNTIL(timeout.isExpired());
6161
}

src/modm/processing/resumable/module.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public:
138138
{
139139
RF_BEGIN();
140140

141-
timeout.restart(new_timeout);
141+
timeout.restart(std::chrono::milliseconds(new_timeout));
142142

143143
if(timeout.isArmed()) {
144144
RF_RETURN(true);

src/modm/processing/timer/module.lb

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def init(module):
1818
def prepare(module, options):
1919
module.depends(
2020
":architecture:clock",
21+
":architecture:assert",
2122
":io",
2223
":math:utils")
2324
return True

src/modm/processing/timer/module.md

+145-108
Original file line numberDiff line numberDiff line change
@@ -1,141 +1,178 @@
11
# Software Timers
22

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`.
46

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:
89

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+
```
2015
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:
2419
25-
!!! warning
26-
Never use these classes when a precise timebase is needed!
20+
```cpp
21+
modm::Timeout timeout{100ms};
2722
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+
```
2939

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:
3344

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+
```
3559
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.
4163
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+
}
4285
```
43-
Event: C S E H P S E H
44-
_____________ ______________
45-
Expired: ______________/ \_______________________/ ...
46-
______ __________
47-
Armed: _______/ \__________________________/ \______________...
48-
_______ ____________
49-
Stopped: \____________________/ \_________________________...
5086

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:
5389

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+
}
5599

56-
time ——————>
57100
```
58101

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:
67107

68108
```cpp
69-
if (timeout.execute())
109+
modm::PeriodicTimer timer{100ms};
110+
void update()
70111
{
71-
// called once after timeout
72-
Led::toggle();
112+
if (timer.execute()) // automatically restarts
113+
{
114+
// blink an LED or something
115+
}
73116
}
74117
```
75118
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:
84123
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+
```
85151

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!
91156

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
97160

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
106162

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:
109165

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.
111169

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:
114172

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.
117177

118-
```cpp
119-
if (timer.execute())
120-
{
121-
// periodically called once
122-
Led::toggle();
123-
}
124-
```
125178

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

Comments
 (0)