はじめに
最近フーリエ変換の勉強をちょっとずつしています。その一歩として、マイコンで三角関数計算などをやったので、それらの処理時間を計測しようとしました。が、no_std環境なのでstd::time::SystemTimeでの計測はできず、timer、interrupt、atomicを使って実現しました。
処理 | 時間(msec) |
---|---|
sin関数100回 | 14 |
微分99回 | 5 |
積分100区間 | 328 |
(内部的には微分は割り算、積分は掛け算の時間を計測しているだけっちゃだけです。)
マイコンでの処理時間を計測できた。 pic.twitter.com/JV0U1zkjXU
— YU2TA7KA (@UGKGbrothers) 2022年2月10日
環境
マイコン:ST Nucleo Board STM32F411RET6
Rust:Ver1.52.1
詳細:【組込みRust】RustでST Nucleo Board STM32F411RET6をLチカさせる - YU2TA7KA's BLOG ~take one step at a time~
ソースコード:GitHub - yu2ta7ka/study_Fourier_analysis: フーリエ解析の勉強用リポジトリ
処理時間の計測方法
- stm32f4xx_halで提供されているタイマーで時間を計測します。
- 設定した周期で割り込みが発生します。
- 割り込み処理で時間計測用のカウンタをインクリメントします。
- 計測したい処理の開始と終了でカウンタを参照して処理時間を計測します。
1. stm32f4xx_halで提供されているタイマーで時間を計測します。
stm32f4xx_hal::timer::Timer - Rustのtim2を使います。第二引数が割り込み周期の設定で1msecごとに割り込み発生するように1,000Hzを設定しました。clocksはこちらの設定を流用します。そして、タイマーをlistenメソッドにてスタートします。また、NVIC (Nested Vectored Interrupt Controller)でTIM2の割り込みを有効化する必要があります。ここはハードの設定を変更するため、unsafeコードになります。
// Set up the interrupt timer // Generates an interrupt at 1 milli second intervals. let mut timer = Timer::tim2(dp.TIM2, 1000.hz(), clocks); timer.listen(Event::TimeOut); // Move the ownership of the period_timer to global. free(|cs| { TIMER_TIM2.borrow(cs).replace(Some(timer)); }); // Enable interrupt stm32::NVIC::unpend(stm32::Interrupt::TIM2); unsafe { stm32::NVIC::unmask(stm32::Interrupt::TIM2); }
2. 設定した周期で割り込みが発生します。
1. で1msecごとに割り込み発生するようになっています。特に実装はありません。
3. 割り込み処理で時間計測用のカウンタをインクリメントします。
割り込み発生時の処理としてインクリメントの処理を実装します。また、再度タイマー割り込みが発生するようにフラグをクリアする実装も必要です。COUNTERについては「アトミック操作」にて後述します。
#[interrupt] fn TIM2() { free(|cs| { if let Some(ref mut tim2) = TIMER_TIM2.borrow(cs).borrow_mut().deref_mut() { // Clears interrupt associated with event. tim2.clear_interrupt(Event::TimeOut); } COUNTER.fetch_add(1, Ordering::Relaxed); }); }
4. 計測したい処理の開始と終了でカウンタを参照して処理時間を計測します。
let start_count = COUNTER.load(Ordering::Relaxed); // something process let end_count = COUNTER.load(Ordering::Relaxed); writeln!(tx, "calculate time {}ms\r", end_count - start_count).unwrap();
以上で処理時間の計測ができるようになりました。
アトミック操作
計測方法の中で説明していなかったCOUNTERについて説明します。COUNTERはグローバルに参照されるため、簡単にはstatic mut で定義する必要があります。この場合、利用時には都度unsafeブロックで囲む必要があります。
static mut COUNTER: u32 = 0; unsafe { COUNTER += 1 };
そこでsafe rustな実装にするために core::sync::atomicを利用して、アトミック操作を実現します。詳細な使い方はこちらの並行性に関する章に記載されています。
// 初期化 static COUNTER: AtomicUsize = AtomicUsize::new(0); // インクリメント COUNTER.fetch_add(1, Ordering::Relaxed); // 参照 COUNTER.load(Ordering::Relaxed);
なお、今回の処理時間計測用のカウンタは割り込み時にしか書き込みを行わないため、static mutな実装でも計測値が異常になる問題は無い認識です。また、アトミック操作以外にMutexを使った手段などもあり並列処理は奥深いです。
おわりに
STM32F4のマイコンで処理時間計測をRustでやりました。timer、interrupt、atomicといろいろ使えて面白かったです。フーリエ解析と並行処理それぞれまだまだ入門感強いので深めていければと思います。
参考
実験しながら学ぶフーリエ解析とディジタル信号処理[Vol.1]
フーリエ解析の概要、マイコン+C言語での演習があります。本記事の処理はこの記事のものです。
組み込みRust開発【stm32f3xx-halの使い方】 - ZeptoElectronicDesign
stm32ファミリのRustのHALクレートを解説してくれています。とてもわかり易いです。
stm32f4xx_hal::timer::Timer - Rust
本記事で使ったTimerのドキュメントです。
Rust embedded. Stopwatch. - mcu.by
ストップウォッチのサンプルです。本記事の発展先です。
以下はno_stdで利用可能なクレートの参考です。
no_stdクレート - Embedded Rust Techniques
Performing Math Functionality - The Embedded Rust Book