Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Monotonic を使った遅延とタイムアウト

最小限のタイミング要件を表現する便利な方法の 1 つは、進行を遅延させることです。

これは monotonic タイマーをインスタンス化することで実現できます(実装については rtic-monotonics を参照してください):

...
    #[init]
    fn init(cx: init::Context) -> (Shared, Local) {
        hprintln!("init");

        Mono::start(cx.core.SYST, 12_000_000);
        ...

_ソフトウェア_タスクは、遅延が満了するのを await できます:

#[task]
async fn foo(_cx: foo::Context) {
    ...
    Mono::delay(100.millis()).await;
    ...
}
完全な例
//! examples/async-delay.rs

#![no_main]
#![no_std]
#![deny(warnings)]
#![deny(unsafe_code)]
#![deny(missing_docs)]

use panic_semihosting as _;

#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)]
mod app {
    use cortex_m_semihosting::{debug, hprintln};
    use rtic_monotonics::systick::prelude::*;

    systick_monotonic!(Mono, 100);

    #[shared]
    struct Shared {}

    #[local]
    struct Local {}

    #[init]
    fn init(cx: init::Context) -> (Shared, Local) {
        hprintln!("init");

        Mono::start(cx.core.SYST, 12_000_000);

        foo::spawn().ok();
        bar::spawn().ok();
        baz::spawn().ok();

        (Shared {}, Local {})
    }

    #[task]
    async fn foo(_cx: foo::Context) {
        hprintln!("hello from foo");
        Mono::delay(100.millis()).await;
        hprintln!("bye from foo");
    }

    #[task]
    async fn bar(_cx: bar::Context) {
        hprintln!("hello from bar");
        Mono::delay(200.millis()).await;
        hprintln!("bye from bar");
    }

    #[task]
    async fn baz(_cx: baz::Context) {
        hprintln!("hello from baz");
        Mono::delay(300.millis()).await;
        hprintln!("bye from baz");

        debug::exit(debug::EXIT_SUCCESS);
    }
}
$ cargo xtask qemu --verbose --example async-delay --features test-critical-section
init
hello from bar
hello from baz
hello from foo
bye from foo
bye from bar
bye from baz

Monotonic の新しい実装への貢献や、monotonic の内部動作に関する詳しい情報に興味がありますか? Implementing a Monotonic の章を確認してください!

タイムアウト

Rust の Future(Rust の async/await を支える仕組み)は合成可能です。これにより、完了した Future の間で select することが可能になります。

一般的なユースケースは、タイムアウトを伴うトランザクションです。以下に示す例では、hal_get(n).await を呼び出すと何らかの想定上のトランザクションを実行する、ダミーの HAL デバイスを導入しています。所要時間は入力パラメーター(n)に基づいて 350ms + n * 100ms としてモデル化しています。

futures クレートの select_biased マクロを使うと、次のようになります:

        // Call hal with short relative timeout using `select_biased`
        select_biased! {
            v = hal_get(1).fuse() => hprintln!("hal returned {}", v),
            _ = Mono::delay(200.millis()).fuse() =>  hprintln!("timeout", ), // this will finish first
        }

        // Call hal with long relative timeout using `select_biased`
        select_biased! {
            v = hal_get(1).fuse() => hprintln!("hal returned {}", v), // hal finish first
            _ = Mono::delay(1000.millis()).fuse() =>  hprintln!("timeout", ),
        }

hal_get の完了に 450ms かかるとすると、200ms の短いタイムアウトは hal_get が完了する前に満了します。

タイムアウトを 1000ms に延ばすと、hal_get のほうが先に完了します。

select_biased を使えば任意の数の futures を組み合わせられるため、非常に強力です。しかし、タイムアウトのパターンは頻繁に使われるため、rtic-monotonics および rtic-time クレートによって提供される、より扱いやすいサポートが RTIC に組み込まれています。以下は別の例で、Mono::delay_untilMono::timeout_after を使用します:

        // get the current time instance
        let mut instant = Mono::now();

        // do this 3 times
        for n in 0..3 {
            // absolute point in time without drift
            instant += 1000.millis();
            Mono::delay_until(instant).await;

            // absolute point in time for timeout
            let timeout = instant + 500.millis();
            hprintln!("now is {:?}, timeout at {:?}", Mono::now(), timeout);

            match Mono::timeout_at(timeout, hal_get(n)).await {
                Ok(v) => hprintln!("hal returned {} at time {:?}", v, Mono::now()),
                _ => hprintln!("timeout"),
            }
        }

ドリフトなしで時間を正確に制御したい場合は、時刻の正確な一点を表す Instant と、時間の区間を表す Duration を使えます。Instant 型と Duration 型に対する操作は fugit クレートによって提供されます。

let mut instant = Mono::now() は、実行の開始時刻を設定します。

この開始時刻を基準に、1000ms ごとに hal_get を呼び出したいとします。これを実現するには、instant を 1000 ms ずつ増やし、その後で Mono::delay_until(instant).await を使います。このループを繰り返す中で発生する追加の遅延は、‘now + 1000’ ではなく ‘previous + 1000’ まで待機することで補償されます(‘now + 1000’ だとループのタイミングがドリフトします)。

上記の select! を使った async タイムアウトの例に対する別の方法として、将来の時点を timeout として定義し、Mono::timeout_at(timeout, hal_get(n)).await を呼び出します。

ループの 1 回目の反復では n == 0 なので、hal_get は 350ms かかり(前述のとおり)、タイムアウト前に完了します。2 回目の反復では遅延は 450ms で、これも依然としてタイムアウト前に完了します。3 回目の反復では n == 2 となり、hal_get の完了には 550ms かかるため、この場合はタイムアウトに達します。

完全な例
//! examples/async-timeout.rs

#![no_main]
#![no_std]
#![deny(warnings)]
#![deny(unsafe_code)]
#![deny(missing_docs)]

use cortex_m_semihosting::{debug, hprintln};
use panic_semihosting as _;
use rtic_monotonics::systick::prelude::*;
systick_monotonic!(Mono, 100);

#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)]
mod app {
    use super::*;
    use futures::{future::FutureExt, select_biased};

    #[shared]
    struct Shared {}

    #[local]
    struct Local {}

    // ANCHOR: init
    #[init]
    fn init(cx: init::Context) -> (Shared, Local) {
        hprintln!("init");

        Mono::start(cx.core.SYST, 12_000_000);
        // ANCHOR_END: init

        foo::spawn().ok();

        (Shared {}, Local {})
    }

    #[task]
    async fn foo(_cx: foo::Context) {
        // ANCHOR: select_biased
        // Call hal with short relative timeout using `select_biased`
        select_biased! {
            v = hal_get(1).fuse() => hprintln!("hal returned {}", v),
            _ = Mono::delay(200.millis()).fuse() =>  hprintln!("timeout", ), // this will finish first
        }

        // Call hal with long relative timeout using `select_biased`
        select_biased! {
            v = hal_get(1).fuse() => hprintln!("hal returned {}", v), // hal finish first
            _ = Mono::delay(1000.millis()).fuse() =>  hprintln!("timeout", ),
        }
        // ANCHOR_END: select_biased

        // ANCHOR: timeout_after_basic
        // Call hal with long relative timeout using monotonic `timeout_after`
        match Mono::timeout_after(1000.millis(), hal_get(1)).await {
            Ok(v) => hprintln!("hal returned {}", v),
            _ => hprintln!("timeout"),
        }
        // ANCHOR_END: timeout_after_basic

        // ANCHOR: timeout_at_basic
        // get the current time instance
        let mut instant = Mono::now();

        // do this 3 times
        for n in 0..3 {
            // absolute point in time without drift
            instant += 1000.millis();
            Mono::delay_until(instant).await;

            // absolute point in time for timeout
            let timeout = instant + 500.millis();
            hprintln!("now is {:?}, timeout at {:?}", Mono::now(), timeout);

            match Mono::timeout_at(timeout, hal_get(n)).await {
                Ok(v) => hprintln!("hal returned {} at time {:?}", v, Mono::now()),
                _ => hprintln!("timeout"),
            }
        }
        // ANCHOR_END: timeout_at_basic

        debug::exit(debug::EXIT_SUCCESS);
    }
}

// Emulate some hal
async fn hal_get(n: u32) -> u32 {
    // emulate some delay time dependent on n
    let d = 350.millis() + n * 100.millis();
    hprintln!("the hal takes a duration of {:?}", d);
    Mono::delay(d).await;
    // emulate some return value
    5
}
$ cargo xtask qemu --verbose --example async-timeout --features test-critical-section
init
the hal takes a duration of Duration { ticks: 45 }
timeout
the hal takes a duration of Duration { ticks: 45 }
hal returned 5
the hal takes a duration of Duration { ticks: 45 }
hal returned 5
now is Instant { ticks: 213 }, timeout at Instant { ticks: 263 }
the hal takes a duration of Duration { ticks: 35 }
hal returned 5 at time Instant { ticks: 249 }
now is Instant { ticks: 313 }, timeout at Instant { ticks: 363 }
the hal takes a duration of Duration { ticks: 45 }
hal returned 5 at time Instant { ticks: 359 }
now is Instant { ticks: 413 }, timeout at Instant { ticks: 463 }
the hal takes a duration of Duration { ticks: 55 }
timeout