メモ:rp-hal(RustでRaspberry Pi Picoをプログラミングするやつ)でタイマーを使いたい

ある関数を定期的に実行したいとき、公式のSDKだと

  • add_repeating_timer_ms()
  • add_repeating_timer_us()

という関数が用意されていて、callback関数を渡すとそれを定期的に実行してくれるらしい。 実際のコードはこのへんとか、RP2040のデータシートの「4.6.4. Timer - Programmer’s Model」あたりにある。

(実際にやってることはこのへんらしい)

で、同じことを Rust でもやりたかったけど、こんなお手軽にやる方法がなかった。 まだよくわかってないけど、難しかったのでとりあえずメモ。

rp-rs/rp-hal-boards のコードを覗いてみる

rp-halのコード例にはtimerを使う例が2つある。見るとわかるが、他のと違ってmain()みたいな関数はなく、mod app#[rtic::app(...)]という謎のmacroがついている。

これはいったい...?、と思っていると、RTIC(Real-Time Interrupt-driven Concurrency)というフレームワークがあってこれを使ってるらしい。

RTIC

基本的な考え方はこのドキュメントがあって、これを読むとなんとなく理解した気持ちになれる。

ざっくり言うとこういう感じのコードになっていて、

  • 初期化処理は #[init] でマークされた関数(ここではinit())の中に書く
  • 共有するリソースは #[shared] でマークし、init()の中で確保する
  • 割り込みの処理をする関数は #[task(bind = TIMER_..., ...)] でマークする

みたいな感じらしい。

#![no_main]
#![no_std]

// ...snip...

#[rtic::app(device = rp_pico::hal::pac, peripherals = true)]
mod app {
    #[shared]
    struct Shared {
        s1: u32,
        s2: i32,
    }

    #[init]
    fn init(c: init::Context) -> (Shared, ...) {

        // ...snip...

        (
            // Initialization of shared resources
            Shared { s1: 0, s2: 1 },

            ...
        )
    }

    #[task(binds = TIMER_IRQ_0, priority = 3, shared = [s1, s2], local = [counter: u32 = 0])]
    fn timer_irq(c: timer_irq::Context) {
        let s1 = c.shared.s1.lock(|s1| s1);

        // ...snip...

    }
}

まだよくわからない点

rp-hal のコード例を見る感じ、割り込み関連やりたいなら rtic を使ってくれ、という感じのようだけど、具体的にどういう形式が想定されてるのかよくわからない。 rp-hal は様々な crate をいい感じに統合して再 export しているものだけど、その中に rtic は入っていない。 現に、cargo-embedprobe-runも、このrticを使ったexampleではうまく動かなくて(プログラムを書き込むのはできるけど、タイムアウトするし、defmt::info!()とかが使えないっぽい?)

あと、間の悪いことに、rticは今まさにversion 2.0をリリースしようとしてるところらしくて、GitHub上にあるコード例はv2だけどcrates.ioにあるのはv1、という状況になっている。 この関数は docs.rs で見ても出てこないけどいったい...、と思ってコミット履歴を追うと v2 の関数だった、みたいなことがけっこうある。

そんなわけで、たぶん rp-hal 側のテンプレートじゃなくて rtic 側のテンプレートから始めた方がよさそうだけど、今がその時期なのかよくわからない。

もひとつわからない点は、このmonotonicという概念だけど、これは単純に知識不足なので勉強するしかなさそう。

なんで LED を定期的に光らせたいというだけでこんなごついコードになってしまうの??、と思うけど、まあちゃんとリソース管理しようと思えばふつうのRustのコードでは所有権回りめんどくさそうなので、こういうフレームワークに任せるしかないのかなあ、というのはまあわかる。 (でも、PIO とか含めて考えると、すべてのピンの所有権を追跡できているわけではないのかも)