割込みの概要と簡単な実例

割込みの概要


マイコンにおける「割込み」という言葉を聞いたことがあると思うが、実際どのようなことが起こっているのかを概念的に説明する。

割込みというのは、その名が示すように、現在実行中のプログラムの流れに割り込んで処理を行い、処理を終了したら元の流れに戻る、ということである。

簡単な図を次に示す。

図1

まず、なんらかの割り込み要求が発生したら、特定の関数を呼び出すように、あらかじめ設定をしておく。

プログラムを実行中(図ではメイン関数)に割り込み要因(スイッチが押されたり、タイマのカウントがオーバーフローしたりなど)が発生すると、登録をしておいた割り込み関数を実行する。

割り込み関数が終了すると、メイン関数の、割り込みが発生した命令の続きに戻る。

注意が必要なのは、割り込みというのは、Cプログラムの行とは関係なく、マシン語の境界で発生することである。当たり前のことであるが、覚えておいてほしい。

図1を注意深く見るとわかるが、メイン関数で使用していたレジスタ(R0 や R1 など)は、割り込み関数の中でも使われている。一見すると、メイン関数の中で使用していたレジスタ値が、割り込みの前後で変わってしまうように思われる。

もしも、このようなことが起こると、割り込みの前後で変数aが勝手に変わってしまうなどの不具合になる。しかし実際には、割り込み関数に入る前にレジスタは退避されて、割り込み関数終了後に復帰されるので、このような不具合は起こらない。

GCC の作法に従って割り込み処理を記述すれば、レジスタの退避と復帰の処理は自動的に追加される。より原始的な開発環境を使用する場合には、割り込みに関する面倒な処理を自分で記述する必要があるかもしれない。

SH7085における割り込み処理


ここでは、MEMEs の SH7085 における割り込み処理の仕組みを説明する。

・割り込み要因

一般的に、割り込みは「外部割込み」と「内部割込み」とに分けることができる。

外部割込みは、ハードウェアに起因する割り込みである。例えば、割り込み端子による割り込み要求やCMT などの内蔵のモジュールによる割り込み要求である。

実際の使い方としては、スイッチによる非常停止、CMT による定時割り込みなどとして使われる。

内部割込みは、ソフトウェアに起因する割り込みである。例えば0除算を行うと発生する割り込みやTRAP 命令による割り込みである。また、存在しない命令(マシン語)の実行でも内部割込みが発生する。

SH7085 においては、言葉の定義が少し異なっており、次のようになっている。

内部割込みと外部割込みをまとめて「例外処理」としていて、例外の要因を

  1. リセット
  2. アドレスエラー
  3. 割り込み
  4. 命令

の 4 つに分類している。(この中で「4.命令」だけが内部割込みである)

これ以降、SH7085 の用語を使って説明をしていくこととする。

・例外処理ベクタテーブル

例外処理(割り込み処理)を行うには、あらかじめベクタテーブルを作成しておかなければならない。ベクタテーブルには、例外が発生した時に呼ばれる処理の先頭アドレスを格納しておく。

例外処理は、NMI であればベクタ番号 11、というように要因とベクタ番号が1対1で決まっている。次の表は代表的な例外要因について、ベクタテーブルを抜粋したものである。

例外要因 ベクタ番号 ベクタテーブルアドレス
パワーオンリセット 0 0x00000000~0x0000003
一般不当命令 4 0x00000010~0x00000013
NMI 11 0x0000002c~0x0000002f
IRQ0 64 0x00000100~0x00000103
IRQ1 65 0x00000104~0x00000107
CMT_0 184 0x000002e0~0x000002e3
CMT_1 188 0x000002f0~0x000002f3

たとえば NMI 要求が発生した場合、CPU は実行中の命令が終了すると、ベクタ番号 11 のアドレス(0x0000002c~0x0000002f)を読み、そこに書かれている値を割り込み処理の先頭番地と解釈し、その番地へジャンプする。

・割り込み優先順位

SH7085 の割り込みには、優先順位がある。複数の割り込みが同時に発生した場合(多重割り込み)には、割り込みコントローラ(INTC)により優先順位が判定され、その判定結果に従って例外処理が起動される。

優先順位は 0~16 で表され、0 が最低順位、16 が最高順位である。16 はマスク(禁止)することのできない最優先の割り込みであり、NMI が該当する。IRQ 端子による割り込みや、内蔵モジュールからの割り込み要求はユーザプログラムにより設定することができる。

種類 優先レベル 備考
NMI 16 優先レベル固定
ユーザブレーク 15 優先レベル固定
IRQ、内蔵周辺モジュール 0~15 割り込み優先レベル設定レジスタにより設定

・割り込みマスクビット

CPU の SR レジスタに割り込みマスクビット(I3~I0)がある。

図2

割り込みが発生すると、設定されている優先レベルと割り込みマスクビットとが比較され、優先レベルのほうが高ければ割り込みは受け付けられる。(同じであれば、受け付けられない)

例えばマスクビット=10 のとき、優先レベル=11 の割り込みは受け付けられるが、優先レベル=10 の割り込みは受け付けられない。

割り込みが受け付けられると、その割り込みの優先順位がマスクビットに設定される。

図3 マスクビット遷移の例

図3で、マスクビット=8 の時に、優先レベル=10の割り込みを受け付けると、マスクビット=10 となる。以降は優先レベル=9 の割り込みは受け付けられない(無視される、又は保留される)が、優先レベル=11 の割り込みは受け付けられる。

優先レベル=11 の割り込み処理が終了すると優先レベル=10の割り込み処理に戻りマスクビットは 10 になる。

さらに、優先レベル=10 の割り込み処理が終了すると、先ほどの優先レベル=9の割り込みが無視されたのであれば、マスクビットは元の値 8 に戻る。

優先レベル=9 の割り込みが保留されていたのであれば、受け付けられる。優先レベル=9 の割り込み処理終了後に、マスクビットは元の値8に戻る。

※優先レベルの低い割り込みが無視されるのか、保留されるのかは、割り込みの種類や設定によって異なる。今回使用している IRQ エッジ割り込みの場合は、保留される。

割り込みを使用した簡単なプログラム


割り込みを使用した、簡単なプログラムを用意したので

int1.tar

からダウンロードして、ワークスペースにコピー後、解凍 (tar -xvf int1.tar) 、make int1 で得られる int1.mot を MEMEs にダウンロードする。

SW4 を押すと LED6 が点灯し、離すと LED6 が消灯するという、簡単なプログラムである。

初期状態では、割り込みを使用しないプログラムになっている。メイン関数の中の無限ループで SW4 を読み込み、LED6 を点灯/消灯している。今までの手法を使ったプログラムである。

  main () {
      :
    while (1) {
      if (SW4 == 1)
        LED6 = LED_ON;
      else
        LED6 = LED_OFF;
    }
  }

次に、プログラムリスト [int1.c] の先頭のほうにある #define USE_INT のコメントアウトをはずして、ビルド、実行をする。

今度は、割り込みを使用したプログラムである。

メイン関数は次のようになり、無限ループの中では何もしていない。

  main() {
      :
    while (1)
      ;
  }

このプログラムでは、SW4 の状態に変化があるたび(押したり離したり)に関数 INT_IRQ0() が呼ばれるように設定してあり、その中で LED の処理を行っている。そのため、メイン関数のなかでは LED を操作していない。

割り込みを使うには、どのような設定をするのか、実際のプログラムを例に簡単に説明する。

1.SW4 が接続されている端子は、IRQ0 入力との兼用端子である。これを IRQ0 入力モードになるように初期設定をしている。

  PFC.PDCRH1.BIT.PD16MD = 2;    // PD16(SW4)端子をIRQ0入力に設定

2.SW4 を押したときと、離したときの両方で割り込みが発生するように設定している。

  INTC.IRQCR.BIT.IRQ0S = 3;     // 立ち上がり、立下りの両エッジ

3.IRQ0 の割り込み優先順位を8に設定している。

  INTC.IPRA.BIT._IRQ0 = 8;      // 割り込み優先レベル = 8

ここまでで、割り込みを発生する側の設定は完了である。

4.CPU のマスクビットを設定しているのが、次の行である。

  set_imask(7);                // マスクビット = 7

これにより、優先レベル8以上の割り込みが受け付けられるようになる。

5.SW4 を押したり離したりすると IRQ0 端子(PD16)が変化し、割り込み IRQ0 が発生する。優先レベルとマスクビットが比較され IRQ0(ベクタ番号64) が受け付けられると、vectors.c のベクタ番号64に書かれている関数 INT_IRQ0() が実行される。

null_int,         /*      TRAP63 */
INT_IRQ0,         /*      IRQ0   */
null_int,         /* 65   IRQ1   */

6.割り込み関数 INT_IRQ0() を次に示す。

// IRQ0(SW4)の割り込み処理関数
#pragma interrupt INT_IRQ0
void INT_IRQ0()
{
  INTC.IRQSR.BIT.IRQ0F = 0; // 割り込み要求をクリア

  if (INTC.IRQSR.BIT.IRQ0L == 1)
    // IRQ0端子 = 1 ... SW4 押されている
    LED6 = LED_ON;
  else
    LED6 = LED_OFF;
}

#pragma interrupt INT_IRQ0 は、INT_IRQ0() という関数が、割り込み処理であることを、コンパイラに伝えている。コンパイラは、この関数に割り込み関数用の特別な処理を施す。

まず関数の先頭で、割り込み要求をクリアしている。

続く if 文で、SW4 の状態を読み込んでいる。PD16 端子は PD.DR.BIT.B16 ビット、又はINTC.IRQSR.BIT.IRQ0L で読み込むことができる。

以上で、設定は終了である。このように、割り込みを使用したプログラムでは、割り込み要求を行う側の設定(割り込み発生の条件、割り込み優先レベル)と、割り込みを受け取る側の設定(マスクビットの設定や、割り込み関数の記述)を行う。

割り込みを使用したプログラムでは、これらの全てを適切に設定しなければならない。割り込み要求を行う側の設定を間違えると、割り込み要求が発生しないし、受け取る側の設定を間違えると、せっかく発生した割り込み要求を受け取ることができない。

割り込みに関するレジスタ


 PDCRH1(ポートDコントロールレジスタH1)

PD18(SW6), PD17(SW5), PD16(SW4) 端子の、動作モード設定を行う。電源投入後の初期状態では、各端子は汎用入出力モードになっているので、これを割り込みモードに設定する。

IRQCR(IRQコントロールレジスタ)

IRQ 端子が、どのような状態の時に割り込み要求を発生するのかを設定する。

SW4~SW6 を押したときに、IRQ0(PD16)~IRQ2(PD18) 端子は H レベルになる。スイッチを押した瞬間に割り込み要求をする場合は立ち上がりエッジを選択、離した瞬間の場合は立下りエッジを選択する。

IRQSR(IRQステータスレジスタ)

割り込み要求の有無を示すフラグと、IRQ端子の状態を示すビットである。

IRQ 端子と PDxx 端子とは兼用になっており、端子の入力レベルは INTC.IRQSR.BIT.IRQxL ビットか、又はPD.DR.BIT.Bxx で確認することができる。

IPR(インタラプトプライオリティレジスタ)

割り込み要因の優先順位(レベル0~15)を設定する。IRQ 端子による割り込みの他に、CMT や MTU などの内蔵モジュールに対してもそれぞれ用意されており、IPRA~IPRF、IPRH~IPRM と多数存在する。

今回使用する IRQ2~IRQ0 端子には IPRA が対応している。

レベル 0 に設定すると、割り込みは発生しなくなる。(マスクレベルにどのような値を設定しても、必ずそれ以下になるため)

演習問題


1.次の割り込みの中で、内部割込みはどれか

  • 内蔵モジュール MTU による割り込み
  • 0 除算で発生する割り込み
  • 外部割込み端子 IRQ0 による割り込み
  • タッチパネルによる割り込み

2.SH7085 における例外の要因を、4つあげよ

3.割り込みマスクビットが 10 であるとき、受け付けられる割り込みの中で最低のレベルはいくつか

4.割り込み関数であることをコンパイラに伝えるために用いられるのは次のどれか

  • set_imask()
  • #pragma interrupt
  • #pragma section
  • get_imask()

5.CPU のマスクレベルを設定する関数は、次のどれか

  • get_imask()
  • set_vbr()
  • trapa()
  • set_imask()

6.NMI を禁止することができない理由を、割り込みレベルと CPU マスクレベルの関係から考えてみよ

7.プログラム int1 を改造し、SW4 が押されるたびに LED6 が点灯→消灯→点灯…するプログラムを作成せよ。割り込みを使用したプログラムにすること。

8.プログラム int1 を改造し、SW4 を押している間 LED6 が点灯し、SW5 が押されるたびに LED5 が点灯→消灯→点灯…するプログラムを作成せよ。割り込みを使用したプログラムにすること。

<ヒント>押している間 → 立ち上がりと、立下りの両方で割り込みを発生。押されるたび → 立ち上がりエッジで割り込み発生。

※IRQ1(SW5) の割り込み関数名は INT_IRQ1 とし、さらに vectors.c の 81 行目を、次のようにしてから make する

INT_IRQ1,      /* 65  IRQ1 */

9.プログラム int1 を改造し、SW4 を押しながら SW5 を押したときに、LED5 が点灯→消灯→点灯…するプログラムを作成せよ。割り込みを使用したプログラムにすること。

<ヒント>SW5 を押しながら SW4 を押しても反応しないこと。どこで割り込みを発生するのが良いか考える。

割り込みが動作しない場合の確認事項


  • 周辺モジュールは、割り込み要求をしているか
  • 割り込み優先レベル、マスクビットの設定は正しいか
  • vectors.c に割り込み関数を登録してあるか(vectors.c 先頭に割り込み関数プロトタイプ宣言も)
  • 割り込み関数内で、割り込み要因をクリアしているか
  • 割り込み関数を #pragma interrupt … 宣言しているか
  • 関係のない割り込みが有効になっていないか(割り込み設定、割り込み関数、vectors.c の関数登録)

Copyright © 2012-2025 ミームス(MEMEs)のサポートページ All rights reserved.
This site is using the Desk Mess Mirrored theme, v2.5, from BuyNowShop.com.