スパイス  組み込み制御装置の受注製作

ライブラリの導入
平成27年 4月 2日

 XC32の割り込み処理の記述
 PIC32をXC32 Cコンパイラで記述するとき、非常に分かり難いのが割り込み処理ルーチンの記述方法です。
とりあえず最初は動けば良しとしてCコンパイラの説明書サンプルをまねて記述しますが、どうも意味がよく分からないので実際に動作させてみて、その意味を類推してみます。

 使用する環境は、CPUはPIC32MX795F512L、コンパイラはXC32 Ver.1.33です。
なお、PIC32MX1xx、PIC32MX2xxにはSRSと呼ばれるシャドウレジスタがないので、この実験結果とは一致しません。
それ以外のPIC32には1組のシャドウレジスタが実装されているようです。

 細かな実験結果を見てもらう前に結論だけを書いておくと、どの程度高速化できるのか良く分からないような機能のために面倒な苦労をする位なら高速化はあきらめて、むしろ汎用的な記述が出来る方法を探した方がメリットが大きいことが大半です。
ここで手間を掛けて実験したのは、意味がよく分からない設定のままでは多少の不安があったためです。割り込みは設定に不備があると、その不備が具体化した段階で暴走します。ペナルティは決して小さくは無いので要注意です。

 ここでの論点は二つあり、一つは速度を度外視して正常動作を優先する設定方法を確定させることです。この方向性では、シャドウレジスタを一切使用しない設定方法を見つけます。
二つ目はシャドウレジスタを使用するとして、どの程度の高速化が見込めそうか大まかな目星をつけることです。
もしどうしても速度が必要な状況が起きたときには、選択肢として取りえるかどうかの参考になります。

 0.お勧めする割り込み処理の使い方
 機能が多すぎるので混乱しがちですが、これらの機能が必要な用途はそれ程多くはありません。むしろ機能を絞って単純な設定で使用する方が全体の見通しは良くなります。
  ・割り込みレベルは1レベルのみを使用し、複数の割り込みレベルを使用することによって発生する多重割り込みを使用しない。
 これによるデメリットは割り込み要求に対する応答の遅れだが、そこまで高速な応答が必要な用途は限られる。割り込み処理関数を「小さく保つ」ことにさえ注意すれば実害は少ない。また、副割り込み優先度を活用すればある程度の優先度管理は可能です。少なくとも、使用する割り込みレベルの数は極力少なくします。

  ・高速化を目的としてシャドウレジスタは使用しない。
 確かに多少の高速化は出来るが、一回の割り込みに対して削減できる命令数は高々30〜40命令程度でしかない。80MHz動作のPIC32では500ns/回程度の高速化のために、シャドウレジスタを使用するのはリスクが大きすぎる。設定を誤ると小さなプログラムでは正しく動作するが、複雑な(多くのレジスタを使用する)プログラムでは暴走するようなバグになる。
シャドウレジスタに関する設定の組み合わせ全てが正常動作する訳ではないので注意が必要です。少なくとも、割り込み処理関数の導入/復帰シーケンスに関して自信を持って判断できるだけの知識を得た後でないと使用しない方が良い。


 1.安定志向の設定方法
 シャドウレジスタを一切使用しない設定方法は比較的簡単です。
  ・割り込み処理関数に記述する__ISR()部のIPLn[SRS|AOTO|SOFT|未記入]の設定部でSOFTまたは未記入を選択します(未記入はSOFTと同等です)。これを全ての割り込み処理関数に対して行います。
  ・IPLn[SRS|AOTO|SOFT|未記入]のnには、その割り込みに対する優先度の設定IPCの設定値と同じ値を設定します。

 これ以外の設定は基本的に無意味です(ConfigビットのFSRSSEL設定は意味を持たない)。
 ここで問題になりそうなのは、割り込みの優先度を動的に変えたい場合には、IPLnの値をどうすれば良いかです。これについては後述します。

このときの割り込み処理関数のリストを示します。
少し分かり難いのですが、基本的に全てのレジスタをスタックに保存しています。それなりに多数のレジスタの退避が行われるので、スタックの使用量は大きくなります。多重割り込みは極力避けたいところです。
!void __ISR(_TIMER_4_VECTOR, IPL1) TMR4ISR(void)
!{
0x9D0000AC: RDPGPR SP, SP
0x9D0000B0: MFC0 K0, EPC
0x9D0000B4: MFC0 K1, Status
0x9D0000B8: ADDIU SP, SP, -112
0x9D0000BC: SW K0, 108(SP)
0x9D0000C0: SW K1, 104(SP)
0x9D0000C4: INS K1, ZERO, 1, 15
0x9D0000C8: ORI K1, K1, 1024    //1024はIPLに"1"を設定の意味、__ISR()部がIPL7なら7168になる
0x9D0000CC: MTC0 K1, Status
0x9D0000D0: SW RA, 92(SP)
0x9D0000D4: SW S8, 88(SP)
0x9D0000D8: SW T9, 84(SP)
0x9D0000DC: SW T8, 80(SP)
0x9D0000E0: SW T7, 76(SP)
0x9D0000E4: SW T6, 72(SP)
0x9D0000E8: SW T5, 68(SP)
0x9D0000EC: SW T4, 64(SP)
0x9D0000F0: SW T3, 60(SP)
0x9D0000F4: SW T2, 56(SP)
0x9D0000F8: SW T1, 52(SP)
0x9D0000FC: SW T0, 48(SP)
0x9D000100: SW A3, 44(SP)
0x9D000104: SW A2, 40(SP)
0x9D000108: SW A1, 36(SP)
0x9D00010C: SW A0, 32(SP)
0x9D000110: SW V1, 28(SP)
0x9D000114: SW V0, 24(SP)
0x9D000118: SW AT, 20(SP)
0x9D00011C: MFLO V0, 0
0x9D000120: SW V0, 100(SP)
0x9D000124: MFHI V1, 0
0x9D000128: SW V1, 96(SP)
0x9D00012C: ADDU S8, SP, ZERO
!       IFS0bits.T4IF = 0;    //ここからがユーザープログラム
0x9D000130: LUI V1, -16504
0x9D000134: LW V0, 4144(V1)
0x9D000138: INS V0, ZERO, 16, 1
0x9D00013C: SW V0, 4144(V1)
!       ligMSTimer++;
0x9D000140: LW V0, -32748(GP)
0x9D000144: ADDIU V0, V0, 1
0x9D000148: SW V0, -32748(GP)
!       if(ligT4CB != NULL)
0x9D00014C: LW V0, -32752(GP)
0x9D000150: BEQ V0, ZERO, 0x9D000164
0x9D000154: NOP
!               (*ligT4CB)();
0x9D000158: LW V0, -32752(GP)
0x9D00015C: JALR V0
0x9D000160: NOP
!}
0x9D000164: ADDU SP, S8, ZERO
0x9D000168: LW V0, 100(SP)
0x9D00016C: MTLO V0, 0
0x9D000170: LW V1, 96(SP)
0x9D000174: MTHI V1, 0
0x9D000178: LW RA, 92(SP)
0x9D00017C: LW S8, 88(SP)
0x9D000180: LW T9, 84(SP)
0x9D000184: LW T8, 80(SP)
0x9D000188: LW T7, 76(SP)
0x9D00018C: LW T6, 72(SP)
0x9D000190: LW T5, 68(SP)
0x9D000194: LW T4, 64(SP)
0x9D000198: LW T3, 60(SP)
0x9D00019C: LW T2, 56(SP)
0x9D0001A0: LW T1, 52(SP)
0x9D0001A4: LW T0, 48(SP)
0x9D0001A8: LW A3, 44(SP)
0x9D0001AC: LW A2, 40(SP)
0x9D0001B0: LW A1, 36(SP)
0x9D0001B4: LW A0, 32(SP)
0x9D0001B8: LW V1, 28(SP)
0x9D0001BC: LW V0, 24(SP)
0x9D0001C0: LW AT, 20(SP)
0x9D0001C4: DI ZERO
0x9D0001C8: EHB
0x9D0001CC: LW K0, 108(SP)
0x9D0001D0: LW K1, 104(SP)
0x9D0001D4: MTC0 K0, EPC
0x9D0001D8: ADDIU SP, SP, 112
0x9D0001DC: WRPGPR SP, SP
0x9D0001E0: MTC0 K1, Status
0x9D0001E4: ERET
もう一つ注目すべきは__ISR()記述部のIPLnのnを書き換えると太字部分の定数が変化します。この値はCP0にあるSTATUSレジスタに書き込まれ、その値からIPL部に相当します。つまり、__ISR()宣言部で宣言しているIPLnのnは割り込み処理関数を実行中のIPL値を設定していることになります。
 ここで一つ目の疑問が出てきます。
割り込みの優先度は個別にIPCレジスタに設定しているので、対応するIPCレジスタの値を自動的に転送すれば済むのに何故ワザワザ割り込み処理関数の属性としてプログラマーに定義させているのか?

 これは単純にMIPSコアの設計上の都合のようです。MIPSコアは機能を単純化するため、コア部分には割り込み機能を持っていません。割り込みに関する機能はコプロセッサCP0を介して外部の割り込みコントローラが行っています。割り込み優先度に関する設定IPCはSFRレジスタ内にあり、割り込みコントローラからはアクセスできないという事情から、このような仕様になったと思われます。あくまでも推測ですが。

 では、割り込み優先度を動的に変更したい場合にはどうすれば良いか?そもそもそんな使い方が出来るのか?

 方法は二つあります。一つ目は副割り込み優先度によって実現します。割り込み優先度を出来るだけ少ないレベルで管理しているなら可能性が高くなります。
 二つ目の方法は、一旦優先度を最高に設定しておいて後から正しいIPCへ設定した優先度に書き直しします。割り込み処理関数を実行中の割り込み優先度はIPCで設定された優先度より低いと問題です。優先度の高い割り込み処理中に低優先度の割り込みを多重に受け付けてしまいます。
が、逆に高い分にはそれ程問題はありません。割り込み関数内のユーザープログラム部分でIPCの優先度を読み出して再設定してやれば、その再設定まで割り込みの受付が遅れるだけで済みます。
//割り込み優先度を動的に変更する場合の割り込み処理関数例

void __ISR(xxx_VECTOR, IPL7SOFT) Func_ISR(void)
{
        uint ip = 対応するIPC読出し関数();
        set_IPL(ip);    //set_IPL():IPLを書き換える関数
    //割り込み処理記述
}
 この方法は、割り込み処理関数をライブラリ化したい場合には有効です。設定値を毎回変更して再コンパイルする必要がなくなります。



 では、シャドウレジスタを使用した場合、どの程度高速化できそうか見てみます。これは、__ISR()記述部をIPL1SRSとした場合のリストです。なお、ConfigビットのFSRSSELの設定値によってもコードが変わりますので参考程度です。正しく動作する保障もありません。
!void __ISR(_TIMER_4_VECTOR, IPL1SRS) TMR4ISR(void)
!{
0x9D0000AC: RDPGPR SP, SP
0x9D0000B0: MFC0 K0, EPC
0x9D0000B4: MFC0 K1, Status
0x9D0000B8: ADDIU SP, SP, -48
0x9D0000BC: SW K0, 44(SP)
0x9D0000C0: MFC0 K0, SRSCtl
0x9D0000C4: SW K1, 40(SP)
0x9D0000C8: SW K0, 36(SP)
0x9D0000CC: INS K1, ZERO, 1, 15
0x9D0000D0: ORI K1, K1, 1024
0x9D0000D4: MTC0 K1, Status
0x9D0000D8: SW RA, 20(SP)
0x9D0000DC: SW S8, 16(SP)
0x9D0000E0: MFLO V0, 0
0x9D0000E4: SW V0, 28(SP)
0x9D0000E8: MFHI V1, 0
0x9D0000EC: SW V1, 24(SP)
0x9D0000F0: ADDU S8, SP, ZERO
!       IFS0bits.T4IF = 0;
0x9D0000F4: LUI V1, -16504
0x9D0000F8: LW V0, 4144(V1)
0x9D0000FC: INS V0, ZERO, 16, 1
0x9D000100: SW V0, 4144(V1)
!       ligMSTimer++;
0x9D000104: LW V0, -32748(GP)
0x9D000108: ADDIU V0, V0, 1
0x9D00010C: SW V0, -32748(GP)
!       if(ligT4CB != NULL)
0x9D000110: LW V0, -32752(GP)
0x9D000114: BEQ V0, ZERO, 0x9D000128
0x9D000118: NOP
!               (*ligT4CB)();
0x9D00011C: LW V0, -32752(GP)
0x9D000120: JALR V0
0x9D000124: NOP
!}
0x9D000128: ADDU SP, S8, ZERO
0x9D00012C: LW V0, 28(SP)
0x9D000130: MTLO V0, 0
0x9D000134: LW V1, 24(SP)
0x9D000138: MTHI V1, 0
0x9D00013C: LW RA, 20(SP)
0x9D000140: LW S8, 16(SP)
0x9D000144: DI ZERO
0x9D000148: EHB
0x9D00014C: LW K1, 36(SP)
0x9D000150: LW K0, 44(SP)
0x9D000154: MTC0 K1, SRSCtl
0x9D000158: LW K1, 40(SP)
0x9D00015C: MTC0 K0, EPC
0x9D000160: ADDIU SP, SP, 48
0x9D000164: WRPGPR SP, SP
0x9D000168: MTC0 K1, Status
0x9D00016C: ERET
 シャドウレジスタを使用することで16個のレジスタの退避・復帰が削除され、また多少の制御コードも削減されています。
しかし、それだけです。
当面はシャドウレジスタを使用しない設定でプログラムし、本当に速度が必要になったときには慎重にシャドウレジスタの使いかたを調べて使用するようにします。



目次へ  前へ  次へ