割り込み処理ルーチンから別の割り込み処理ルーチンを呼ぶ

ちょっと長くなりますが、悩んでいますので、ご存じの方がおられたらよろしくお願いします。
内容ですが、以下のようなことです。

ある割り込み処理ルーチンAから、別の割り込み処理ルーチンBを呼ぶようなコードが書かれていました。
おそらくこのようなコードでは、PSWが破壊されると思いますが、割り込みがかかったメイン処理ルーチンmainに
なぜに影響があるのかが理解できません。でも、実際に、AからBを呼ばないようにすると、安定して動作するのです。
以下はその現象のサンプルです。


#pragma interrupt A_interrupt(vect=XX1)
#pragma interrupt B_interrupt(vect=XX2)

static void A_interrupt(void)
{
      B_interrupt();
}

static void B_Interrupt(void)
{
    // 処理
    // ..
}

static unsigned char ip=0;
static unsigned char op=0;

void main()
{
   while(1){
      if( ip != op ) {
         sub_func();
      }
   }
}

条件は以下となります。
Aの割り込みは頻繁にかかります。
Bの割り込みは決してかかりません。

上記処理では、ip,opが変化がないとすると、main()では
決してsub_func()は呼ばれないはずですが、Aの割り込みがかかると
なぜかsub_func()が呼ばれるようになります。
そして、Aの割り込み処理からBの割り込み処理を呼ばないようにすると
sub_func()は呼ばれないようになります。

動作の推察:
1.Aの割り込みでは、スタックに PSW(1バイト)、PC(3バイト)が保存されます。
2.AからBを呼ぶと、スタックに、0値(1バイト)、PC(3バイト)が保存されます。
3.B処理から戻るときRETIが実行され、PSWには0が取り込まれて、コールした時とは異なる値でPSWが破壊されます。
4.Aの処理終わりで、再びRETIが実行され、スタック上に保存されたPSWが復帰されます。

以上の状態となると、main()でおかしな動作にはならないと思われるのです。
ちゃんと、Aの割り込みの終わりで、PSWが戻っているので。
でも、実際にはPSWが破壊されて、おかしな動作になっていると思われるのです。

どうして、上記のような処理でおかしくなるのか、どなたかご存じでしょうか。

実を言えば、Aから割り込み処理Bを呼んだ事が不具合の原因だと思われ、呼ばないようにすると改善することはテストで確認しました。

しかし、CPUの動作仕様からすると、不具合も起こらないはずにしか思えません。
これでは、本当に不具合が修正できているのかわからないのです。

Top Replies

Parents Reply Children
  • 割り込みハンドラ(割り込みベクタ)からの復帰と通常の関数(サブルーチン)からの復帰はCPU内部で動作が違うのでコンパイルされた関数からの戻りとして使われる機械語が違います。RL78ならRETIとRETがあり、割り込みハンドラではRETIが戻りとして使われるので割り込みハンドラを直接関数のように呼び出すと異常な状態になります。もし、ある割り込みハンドラの内部処理を他の割り込みや通常の処理から呼び出したいならその処理を関数として作り直して呼び出すようにすれば解決します。

    www.renesas.com/.../78k0-series-instructions

  • Yamamoto様
    ご回答ありがとうございます。

    リンクを頂いている78K/0シリーズと、今回使用しているRL78/F13とは仕様が異なるようです。
    リンクされている78K/0シリーズでは、スタックをCALLで2バイト、割り込みでは3バイト消費し、RETでは2バイト戻し、RETIでは3バイト戻しますので、SPがずれてしまい明らかにおかしな動作になるでしょう。
    しかし、RL78/F13では、PCは20ビットあり、CALLでも、割り込みでもSPは4バイト消費します。つまりSPに関してはずれが無い仕様なのです。

    したがって、今回の問い合わせの場合、そのようなコードを書いても不具合にならないように見えるのが問題なのです。
    つまり、

    1.割り込み処理ルーチンから割り込み処理ルーチンをコールしたことで不具合が発生するのか?
    2.発生するとしたらどのような仕組みから起こっているのか。
    3.Bを割り込み処理ルーチンから通常のサブルーチンにしたら改善することが証明できるか?

    ということが知りたいのです。

  • わわいです

    それが知りたいのなら、デバッガで、割り込み関数の入り口でブレークポイントを設定し、逆アセンブリレベルでワンステップづつ実行させてみましょう。

    そうやってレジスタの内容、コードの挙動などを追いかけていけば何が起こるのか、がわかると思いますよ

  • 78K/0しかオペコード一覧がわかるのが見つからなくて、わかりづらくてすみません。

    割り込み時はPSWの待避動作があります。RETIとRETBは呼ばれるとPSWをスタックから値を取り出してPSWにセットします。RETはそういう動作をしません。

  •  チョコです。

    これは、懐かしい「RAM パリティ・エラー検出」によるリセットがかかっているものと考えられます。

    CALL命令では、PSWをスタックには書かれていないので、RAMの値は不定、それをRETIで読み出したものだから、たまたまパリティのエラーが発生したてたものでしょう。そのため、リセットがかかり、動作がおかしくなったものです。

    この状態はデバッガでも確認は難しいですね。

    以上

  • かわい様

    返信ありがとうございます。

    使用している開発環境のe2Studioでは、逆アセンブル表示はできることがありますが、

    そのアセンブラリスト上でのステップ実行はできないようです。

    なにか方法があるんでしょうか?

  • チョコ様

    返信ありがとうございます。

    RL78/F13には、RAMのパリティチェック機能はないようです。ハードウェアマニュアルには記載がありませんでした。

    ただし、ECC機能はあるようですが、ECCチェックエラーで割り込みを発生できるのは

    デバック中でないときのみのようです。現在は開発中なので、常時デバッカーをつないで動作させています。

    それに、この機能は未初期化のメモリアクセスをチェックするものではなくて、RAMのハード的なビット化けのチェックらしいので、今回の件には当てはまらないでしょう。

  • わわいです

    逆アセンブリリスト上でもブレークポイントを設定できますんで、それでいろいろやってみてください

  • チョコです。

    >それに、この機能は未初期化のメモリアクセスをチェックするものではなくて、RAMのハード的なビット化けのチェックらしいので、今回の件には当てはまらないでしょう。

    RL78/F13のハードウェア マニュアルをECCで検索してみたのですが、DTCの所に以下のような記述がありました。ECCは通常は、データ領域に対して使うものですが、どこにもアドレスの記述はありません。DTCでの読み出しで割り込みが発生するなら、RETIでの読み出しでも同様ではないかと考えられます。

    ECCの所には、以下のような記述もあります。

    割り込みの設定をどうしているかは、分かりませんが、スタック領域は初期化しておいた方がいいのではないかいと思います。確かスタートアップにスタック領域の初期化サブルーチンがあり、そこを呼ぶところがコメントアウトされていたような記憶があります。

    以上

  • ちょっと気になったので、実際にどの様に動くかやってみました。

     簡単な割り込み関数を定義して動作を見る限り、mus.さんの仰っている通り、メイン関数に戻るところでスタックに書かれている値をPSWに戻すので、メイン関数の動作がおかしくなるという現象は見られませんでした。

     そこで、どのようなケースでおかしくなるか。意図的に動作がおかしくなるなケースを作ってみました。

    #pragma interrupt r_tau0_channel0_interrupt(vect=INTTM00, bank=RB1) //割り込み関数A
    #pragma interrupt r_tau1_channel0_interrupt(vect=INTTM10) //割り込み関数B

     割り込み関数Aに相当するのは、r_tau0_channel0_interrupt()です、この関数にレジスタバンクの設定を行っています。割り込み関数Bに相当するのがr_tau1_channel0_interrupt()です。

     普通に、割り込み関数Aから割り込み関数Bを呼ぶと、コンパイラがエラーを返しましたので、Aからはdummy_func()(通常関数)を呼ぶ様にしておき、後からdummy_func()のCALL命令のアドレスを割り込み関数Bに置き換えました。

    static void __near r_tau0_channel0_interrupt(void)
    {
      /* Start user code. Do not edit comment generated here */
      g_tau0_count++;
      P7_bit.no1 = ~P7_bit.no1;
      dummy_func();  //これの実体をr_tau1_channel0_interrupt()に置き換え
      /* End user code. Do not edit comment generated here */
    }

     CALLで割り込み関数Bを呼んだ時は、RL78のソフトウェアマニュアルでは、0x00とPC 3バイトがスタックに保存されると書いてある「RL78 ユーザズマニュアル ソフトウェア編 図4-25 CALL, CALLTの例」のですが、デバッガでステップ実行する限りでは、0x00は書かれませんでした(元から入っている値を書き換えない)。もちろん、割り込み関数BのRETIではSP+3のデータをPSWに戻していました。

     このとき(PSWの値を不正に戻す動作)、割り込み関数Aがレジスタバンク指定していれば、割り込み関数BのRETIの際に、PSWの値がおかしくなり、RBS0=RBS1=0となる事があり、メイン関数の汎用レジスタを壊しに行く動作となります。

     具体的には、66行目、176Hの"POP AX"で、レジスタバンク0の(=メイン関数で使用している)AXが上書きされています。(POP AX実行前は、AX=1でした)

     メイン関数では

     AXが上書きされたので、比較命令で期待値と逆の結果になり、呼ばれるはずのないsub_func()が呼ばれました。(この例では、AXが0で上書きされたので、初期値a=b=1と、わざわざ動作がおかしくなる様に誘導しています。)

     デバッガでステップ実行した場合、呼ばれるはずのないsub_func()が実行されました。また、motファイルを(CALL命令のアドレスを割り込み関数に)書き換えてROMに書き込み、デバッガ接続しない状態で実行してみましたが、この場合もsub_func()が呼ばれました。

     割り込み関数をレジスタバンク指定すれば、メイン関数でおかしな動作が起こる事が、容易に再現しました。

     …オリジナルのプログラムで、レジスタバンクを使っていないとすれば、別な原因かと思いますが。

    (Zフラグではなく、RBSが悪さをしているということも考えられるかと思います。)