メモ: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-embed
もprobe-run
も、このrticを使ったexampleではうまく動かなくて(プログラムを書き込むのはできるけど、タイムアウトするし、defmt::info!()
とかが使えないっぽい?)
あと、間の悪いことに、rticは今まさにversion 2.0をリリースしようとしてるところらしくて、GitHub上にあるコード例はv2だけどcrates.ioにあるのはv1、という状況になっている。 この関数は docs.rs で見ても出てこないけどいったい...、と思ってコミット履歴を追うと v2 の関数だった、みたいなことがけっこうある。
そんなわけで、たぶん rp-hal 側のテンプレートじゃなくて rtic 側のテンプレートから始めた方がよさそうだけど、今がその時期なのかよくわからない。
もひとつわからない点は、このmonotonicという概念だけど、これは単純に知識不足なので勉強するしかなさそう。
なんで LED を定期的に光らせたいというだけでこんなごついコードになってしまうの??、と思うけど、まあちゃんとリソース管理しようと思えばふつうのRustのコードでは所有権回りめんどくさそうなので、こういうフレームワークに任せるしかないのかなあ、というのはまあわかる。 (でも、PIO とか含めて考えると、すべてのピンの所有権を追跡できているわけではないのかも)