今まであまり使うことがなかったのがマイコンのスリープ機能である。
低消費電力化が求められるシステムなら当然使うんだろうけど。
割り込みが来るまで処理を止めるという話ですね。
で、ARMの場合、wfi命令というのが用意されている。
この命令を実行すれば割り込みが来るまでお休みになる。
while(1){
if(flag==0){
__asm("wfi");
}
if(flag==1){
...
割り込み処理でflag変数に何か入るまでループで待つというのを、
flag変数が0ならばwfi命令を実行して、割り込みが入るまでスリープ状体でお休みと。
wfi命令とif(flag==1)の間で割り込みが実行されることを期待しているわけだ。
でも、if(flag==0)とwfi命令の間に割り込みが入ったらどうするのだろう?
ここでは省略したのだが、実際のコードではこの間にUARTの受信割り込みを有効化するための処理が入っている。
フラグを見て直後にwfi命令を実行していると言い切れない部分もある。
そんなわけでwfi命令の使用例を調べてみると、こんなアセンブラコードが見つかった。
cpsid i /* 割り込みマスク */
mov r4, #0
msr basepri, r4 /* 全割り込み許可 */ wfi
cpsie i /* 割り込み受付 */
wfi命令は割り込み待ちの命令だが、その直前に割り込みマスクをして、
それでwfiを出たら割り込み受付をするという書き方である。
これを見てもわかるのだが、wfi命令というのは割り込み待ちが発生した時点で解除される。
なので割り込みマスク状態で動かしてもOKなんですね。
むしろそのように書くことでwfi命令に入るまでに割り込みが消化されてしまうリスクがなくなる。
というわけで、さっきのflag==0ならwfiで割り込み待ちというのは、
while(1){
__asm("cpsid i");
if(flag==0){
__asm("wfi");
}
__asm("cpsie i");
if(flag==1){
...
と書くとよい。
こうすればif(flag==0)とwfi命令の間に割り込まれる心配がなくなる。
このあたり厳密にやらなくてもだいたい動いてしまうんですけどね。
さっきのアセンブラコードの場合、wfi命令の前に割り込み優先度の操作をしている。
このため割り込み優先度の操作をした途端に割り込まれる可能性がある。
なので、一旦割り込み禁止にして、優先度を変えているんですね。
で、優先度を変えたことで割り込み待ちになればwfi命令は即抜けになる。
割り込みマスクを解除したときに割り込み処理が走るというわけ。
このコードを見る前から割り込みマスク中でもwfi命令から抜けるらしい、
という話は見ていて、一体何のためにそうなってるんだろうと思ったら、
こういう使い方をするためだったんですね。
もちろん割り込み有効状態でwfi命令を使っても、それはそれで動くのだが、
割り込みマスク状態で使って、wfi命令を抜けたら割り込みマスクを解除するという動きの方がより正確な動きのようである。
あと、これは特に関係ないが、wfi命令を検索するとwfe命令のことがひっかかる。
割り込みが発生するまでスリープするという目的ではwfi命令を使うのだが、
wfe命令というのは他のコアでsev命令が実行されるまで待つことを目的としている。
wfeは”Wait For Event”、sevは”Set Event”で一対の命令である。
ある変数が書き換わるまでポーリングするという処理をするのに、
wfe命令で待って、他コアで書き換えたらsev命令を実行するような使い方をするらしい。
sevl 1: wfe
ldr r0, [r1]
cbz r0, 1b
sevl命令は他コアでsev命令が実行されてなくても、次のwfe命令は抜けるという意味になる。
(sevlは”Set Event Locally”の意味で、自コアだけイベントをセットする意味)
1回はr1レジスタのアドレスに格納されたフラグをチェックして、
そのフラグが0ならばwfe命令に戻って待つという記述である。
で、この間に割り込みが入るか、他コアでsev命令が実行されればwfe命令を抜けると。
割り込みが入るまで待つという部分はwfi命令とも共通するのだが、使用目的が異なる。
他コアからのフラグをポーリングしている間でも割り込みは入らないと困るから抜けるだけである。
これは使わなさそうな感じがするが、そういうのあるという余談である。