Rust, SOLID-OSで割り込み処理[フィードバック編](連載21)
前回書いたプログラムや内容について、有識者の方に読んでいただき、フィードバックを頂きました。
今回はその内容について記載します。
1.共有変数を構造体で定義
前回、コールバック関数との共有変数をタプル形式で定義しましたが、読みにくくなりました。
共有変数すべてを一つの構造体でまとめる書き方について教えて頂きましたので、紹介します。
コードの書き方は色々あると思いますが、筆者の場合は以下を共有変数としています。
・送信データ格納バッファ
(前回は3バイトだったが、よく考えると2バイトで大丈夫。)
・受信データ
・転送開始直後のTX-FIFOエンプティ割り込みなのかどうか
・レジスタリード関数なのかライト関数なのか
・SPI転送完了したかどうか
これらを構造体にまとめます。
struct XferState {
send_data: [AtomicU8; 2],
recv_data: AtomicI32,
is_first: AtomicBool,
is_read: AtomicBool,
is_done: AtomicBool,
}
そして、グローバル変数として使えるよう、静的インスタンス化します。
static XFER_STATE: XferState = XferState{
send_data: [AtomicU8::new(0),AtomicU8::new(0)],
recv_data: AtomicI32::new(0),
is_first: AtomicBool::new(false),
is_read: AtomicBool::new(false),
is_done: AtomicBool::new(false)
};
この構造体を、コールバック関数とSPI転送(リード・ライト)関数で共有することにしました。
実は先週、タプル形式で書く前に、構造体で書こうとして、あれこれ試行錯誤して失敗していました。
構造体の中の配列を初期化するところで、
[AtomicU8::new(0),AtomicU8::new(0)]
と書く事を思いつけずにウンウン唸っていました。
言われてみれば、あぁそうか、と思えるのですが、、、
#ソースコードは別途ダウンロードできるようにします。
2.割り込みハンドラの書き方
お気づきの方も多いかと思いますが、筆者の書いた割り込みハンドラは、「割り込み」の特色を全く生かしていないものです。
目的 ⇒「RustでSOLID-OSの割り込み機能を使うこと!」
であったため、「割り込みハンドラの在り方」については気にしていませんでした。
これで終わってしまっては、なんだかイマイチです。
(まぁ指摘受けたからそう言っていますが、、別にいいかなぁ♪なんて思ってました。)
では、どうあるべきか。
せっかくリアルタイムOS上で動いているんです。
待ってる間は自タスクをスリープにすべきでしょう。
具体的に書きます。
SPI転送(リード・ライト)関数は、転送開始後、転送完了フラグをポーリングすることで転送終了を待っています。これでは、CSレジスタのDONEビットをポーリングするのと大して違いありません。
せっかく転送終了したら割り込みハンドラが起動するのだから、その起動した割り込みハンドラによって起こしてもらうようにすべきです。
SPI転送(リード・ライト)関数:
SPI転送を開始(TAビットを1)したら寝る。
割り込みハンドラ:
転送終了したら、SPI転送(リード・ライト)関数を起こす。
実装方法は以下のような感じになりそうです。
タスクスリープ対応SPI転送(リード・ライト)関数:
①自タスクIDを取得する。
②取得したIDをアトミックなグローバル変数に格納する
③既存のSPI転送開始処理を行う
④スリープする
⑤タスクIDを取り除く
割り込みハンドラ:
①既存の処理を行う
②転送が終了していれば、
・アトミックなグローバル変数から対象のタスクID値を取得
・対象のタスクをwakeupさせる。
ひとつひとつ、コード例を教えてもらったので、見ていきます。
2.1 対象タスクID格納するためのグローバル変数
アトミックとして、共有できるようにします。
/// アクティブなSPI転送の発行元タスクIDを格納するグローバル変数。
/// 制御機構を独占するためのミューテックスの役割も兼ねる。
static XFER_ACTIVE_TID: AtomicI32 = AtomicI32::new(0);
2.2 [SPI転送関数]自タスクのIDを取得する
itron関数を使用してIDを取得します。
let mut tid = 0;
assert_eq!(unsafe { itron::abi::get_tid(&mut tid) }, itron::abi::E_OK);
assert_ne!(tid, 0);
2.3 [SPI転送関数]取得したタスクのIDを格納する
先程準備したグローバル変数に格納します。
XFER_ACTIVE_TID.compare_exchange(0, tid, Ordering::AcqRel, Ordering::Relaxed)
.expect("transfer already in progress");
2.4 [SPI転送関数]スリープさせる
itron関数のslp_tsk()を使用します。
assert_eq!(unsafe { itron::abi::slp_tsk() }, itron::abi::E_OK);
2.5 [SPI転送関数]タスクIDを取り除く
先程準備したグローバル変数に格納されたタスクIDを取り除きます。
XFER_ACTIVE_TID.store(0, Ordering::Release);
2.6 [ハンドラ]対象のタスクをスリープ解除
グローバル変数から対象のタスクIDを取得し、itron関数のwup_tsk()を使用しwakeupさせます。
let tid = XFER_ACTIVE_TID.load(Ordering::Acquire);
if tid != 0 {
unsafe { itron::abi::wup_tsk(tid) };
}
コード例っていうか。。。
そのまま動くやん!
無事、動作確認ができました!
割り込みハンドラで、対象タスクのスリープ解除をする直前でブレークしてみました。
SOLID-IDEのRTOSビューアで見てみると、スリープになっている事がわかります。
#ご紹介した以外にも、イベントフラグを用いる方法もあります。
3.割り込み優先度の設定
前回、SPI転送完了割り込みの優先度について、とりあえず 10 固定にしました。
今回は、SOLID-OSのSOLID_INTC_GetPriorityLevel()関数を使って、システムで許されている最大値を取得し、その付近の値を優先度として設定してみます。
SOLID_INTC_GetPriorityLevel()関数は、solidクレートの中で、C言語の関数をコールするためのWrapperが定義されています。。
https://github.com/KyotoMicrocomputer/solid-rapi4-examples/blob/main/common/solid/src/abi.rs
solidクレートの中の、abiの中にあるので、
solid::abi::SOLID_INTC_GetPriorityLevel()
で使用できます。
さらに、C言語なので戻り値がc_int型です。
i32型に変換するため、
.into()
を付けます。
すると、今度は unsafeだよ!と怒られました。
という事で、unsafe{ }で囲んだところ、エラーがなくなりました。
let spi_handler_option = interrupt::HandlerOptions::new(interrupt::Number(150), unsafe{solid::abi::SOLID_INTC_GetPriorityLevel().into()});
このコードを実行してみます。
max_priorityには何が入っているでしょうか。
16ですね。
– 2 として、優先順位14あたりがいいかもしれません。
let max_priority: i32 = unsafe{solid::abi::SOLID_INTC_GetPriorityLevel().into()};
let spi_handler_option = interrupt::HandlerOptions::new(interrupt::Number(150), max_priority - 2);
4.おまじないの謎コード:pin_singleton、CpuCx
前回、
「singleton::pin_singleton, thread::CpuCxについては、今回は「おまじない」という事でご容赦ください。。。」
で ごまかした ご容赦頂いた部分ですが、フィードバック頂きました。
ものすごく簡単にイメージだけで言うと、
・pin_singleton!
プログラムを実行中に1回だけ走らせるという事ができ(シングルトン)、かつ、割り込み構造体等ずっと同じメモリに留め置く(ピン)型のみ受け付けるマクロ。ハードウェアの初期設定時に使うと便利。
・CpuCx<'a>
SOLID_CPU_CONTEXTのラッパー。
以下、下手に自分の言葉で書こうとせず、フィードバック頂いた内容をそのまま引用することにします。
5.ソースコードダウンロード
以上ここまでで、Rust×SOLID-OS×SPI加速度センサとの格闘はひと段落です。
今までのソースコードを、以下からダウンロードできます。
何かのご参考になれば幸いです。
PC側C#プログラム(Visual Studio 2019系)
(bin\Releaseフォルダ内にexecutableファイルがあります)
加速度センサ制御部はクレート化しました。
SPI割り込みハンドラ等の部分については、spi_acxelrl; クレートのソースコードに入っています。
次回は、SOLID-OSの持つ他のAPIをRustからコールしてみます。
C言語で作られたAPIですが、今回使用してみたSOLID_INTC_GetPriorityLevel()関数のように、solidクレートとしてRust側に解放されているので、それらを使用してみつつ、RustとC言語の橋渡しの部分について理解を深められるといいなと思っています。