aboutsummaryrefslogtreecommitdiff
path: root/book/en/src/by-example/delay.md
blob: a6ad0e0a0224542e34616683aeba2ed1f6fb1253 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# Tasks with delay

A convenient way to express miniminal timing requirements is by delaying progression. 

This can be achieved by instantiating a monotonic timer (for implementations, see [`rtic-monotonics`]):

[`rtic-monotonics`]: https://github.com/rtic-rs/rtic/tree/master/rtic-monotonics
[`rtic-time`]: https://github.com/rtic-rs/rtic/tree/master/rtic-time

``` rust,noplayground
...
{{#include ../../../../rtic/examples/async-timeout.rs:init}}
        ...
```

A *software* task can `await` the delay to expire:

``` rust,noplayground
#[task]
async fn foo(_cx: foo::Context) {
    ...
    Systick::delay(100.millis()).await;
    ...
}

```

<!-- TODO: move technical explanation to internals -->

Technically, the timer queue is implemented as a list based priority queue, where list-nodes are statically allocated as part of the underlying task `Future`. Thus, the timer queue is infallible at run-time (its size and allocation are determined at compile time).

Similarly the channels implementation, the timer-queue implementation relies on a global *Critical Section* (CS) for race protection. For the examples a CS implementation is provided by adding `--features test-critical-section` to the build options.

<details>
<summary>A complete example</summary>

``` rust,noplayground
{{#include ../../../../rtic/examples/async-delay.rs}}
```

``` console
$ cargo run --target thumbv7m-none-eabi --example async-delay --features test-critical-section 
```

``` console
{{#include ../../../../rtic/ci/expected/async-delay.run}}
```

</details>

## Timeout

Rust [`Future`]s (underlying Rust `async`/`await`) are composable. This makes it possible to `select` in between `Futures` that have completed.

[`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html

A common use case is transactions with an associated timeout. In the examples shown below, we introduce a fake HAL device that performs some transaction. We have modelled the time it takes based on the input parameter (`n`) as `350ms + n * 100ms`. 

Using the `select_biased` macro from the `futures` crate it may look like this:

``` rust,noplayground,noplayground
{{#include ../../../../rtic/examples/async-timeout.rs:select_biased}}
```

Assuming the `hal_get` will take 450ms to finish, a short timeout of 200ms will expire before `hal_get` can complete.

Extending the timeout to 1000ms would cause `hal_get` will to complete first.

Using `select_biased` any number of futures can be combined, so its very powerful. However, as the timeout pattern is frequently used, more ergonomic support is baked into RTIC, provided by the [`rtic-monotonics`] and [`rtic-time`] crates. 

Rewriting the second example from above using `timeout_after` gives:

``` rust,noplayground
{{#include ../../../../rtic/examples/async-timeout.rs:timeout_at_basic}}
```

In cases where you want exact control over time without drift we can use exact points in time using `Instant`, and spans of time using `Duration`. Operations on the `Instant` and `Duration` types come from the [`fugit`] crate.

[fugit]: https://crates.io/crates/fugit

``` rust,noplayground

{{#include ../../../../rtic/examples/async-timeout.rs:timeout_at}}

```

`let mut instant = Systick::now()` sets the starting time of execution. 

We want to call `hal_get` after 1000ms relative to this starting time. This can be accomplished by using `Systick::delay_until(instant).await`. 

Then, we define a point in time called `timeout`, and call `Systick::timeout_at(timeout, hal_get(n)).await`. 

For the first iteration of the loop, with `n == 0`, the `hal_get` will take 350ms (and finishes before the timeout). 

For the second iteration, with `n == 1`, the `hal_get` will take 450ms (and again succeeds to finish before the timeout).  

For the third iteration, with `n == 2`, `hal_get` will take 550ms to finish, in which case we will run into a timeout.

<details>
<summary>A complete example</summary>

``` rust,noplayground
{{#include ../../../../rtic/examples/async-timeout.rs}}
```

``` console
$ cargo run --target thumbv7m-none-eabi --example async-timeout --features test-critical-section 
```

``` console
{{#include ../../../../rtic/ci/expected/async-timeout.run}}
```
</details>