割込みの概要と簡単な実例
割込みの概要
マイコンにおける「割込み」という言葉を聞いたことがあると思うが、実際どのようなことが起こっているのかを概念的に説明する。
割込みというのは、その名が示すように、現在実行中のプログラムの流れに割り込んで処理を行い、処理を終了したら元の流れに戻る、ということである。
簡単な図を次に示す。
図1
まず、なんらかの割り込み要求が発生したら、特定の関数を呼び出すように、あらかじめ設定をしておく。
プログラムを実行中(図ではメイン関数)に割り込み要因(スイッチが押されたり、タイマのカウントがオーバーフローしたりなど)が発生すると、登録をしておいた割り込み関数を実行する。
割り込み関数が終了すると、メイン関数の、割り込みが発生した命令の続きに戻る。
注意が必要なのは、割り込みというのは、Cプログラムの行とは関係なく、マシン語の境界で発生することである。当たり前のことであるが、覚えておいてほしい。
図1を注意深く見るとわかるが、メイン関数で使用していたレジスタ(R0やR1など)は、割り込み関数の中でも使われている。一見すると、メイン関数の中で使用していたレジスタ値が、割り込みの前後で変わってしまうように思われる。
もしも、このようなことが起こると、割り込みの前後で変数aが勝手に変わってしまうなどの不具合になる。しかし実際には、割り込み関数に入る前にレジスタは退避されて、割り込み関数終了後に復帰されるので、このような不具合は起こらない。
HEWの作法に従って割り込み処理を記述すれば、レジスタの退避と復帰の処理は自動的に追加される。より原始的な開発環境を使用する場合には、割り込みに関する面倒な処理を自分で記述する必要があるかもしれない。
SH7085における割り込み処理
ここでは、MEMEsのSH7085における割り込み処理の仕組みを説明する。
・割り込み要因
一般的に、割り込みは「外部割込み」と「内部割込み」とに分けることができる。
外部割込みは、ハードウェアに起因する割り込みである。例えば、割り込み端子による割り込み要求やCMTなどの内蔵のモジュールによる割り込み要求である。
実際の使い方としては、スイッチによる非常停止、CMTによる定時割り込みなどとして使われる。
内部割込みは、ソフトウェアに起因する割り込みである。例えば0除算を行うと発生する割り込みやTRAP命令による割り込みである。また、存在しない命令(マシン語)の実行でも内部割込みが発生する。
SH7085においては、言葉の定義が少し異なっており、次のようになっている。
内部割込みと外部割込みをまとめて「例外処理」としていて、例外の要因を
- リセット
- アドレスエラー
- 割り込み
- 命令
の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.zip
からダウンロードして、そのままビルドし、実行する。SW4を押すとLED6が点灯し、離すとLED6が消灯するという、簡単なプログラムである。
初期状態では、割り込みを使用しないプログラムになっている。メイン関数の中の無限ループでSW4を読み込み、LED6を点灯/消灯している。今までの手法を使ったプログラムである。
main () { : while (1) { if (SW4 == 1) LED6 = LED_ON; else LED6 = LED_OFF; } }
次に、プログラムリスト[int1_1.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が受け付けられると関数INT_IRQ0()を呼び出すように設定してある(ベクタテーブルに関数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.HEWにおいて、割り込み関数であることをコンパイラに伝えるために用いられるのは次のどれか
- set_imask()
- #pragma interrupt
- #pragma section
- get_imask()
5.HEWにおいて、CPUのマスクレベルを設定する関数は、次のどれか
- get_imask()
- set_vbr()
- trapa()
- set_imask()
6.NMIを禁止することができない理由を、割り込みレベルとCPUマスクレベルの関係から考えてみよ
7.プログラムint1_1を改造し、SW4が押されるたびにLED6が点灯→消灯→点灯…するプログラムを作成せよ。割り込みを使用したプログラムにすること。
8.プログラムint1_1を改造し、SW4を押している間LED6が点灯し、SW5が押されるたびにLED5が点灯→消灯→点灯…するプログラムを作成せよ。割り込みを使用したプログラムにすること。
<ヒント>押している間 → 立ち上がりと、立下りの両方で割り込みを発生。押されるたび → 立ち上がりエッジで割り込み発生。
※IRQ1(SW5)の割り込み関数名はINT_IRQ1とし、プロジェクトを構成している”intprg.c”の140行目を、次のようにコメントアウトしてからビルドする
//void INT_IRQ1(void){/* sleep(); */}
これは、最初から用意されている割り込み処理関数である。今回は、int1_1.c内に関数を用意したので、こちらはコメントアウトしておく。
9.プログラムint1_1を改造し、SW4を押しながらSW5を押したときに、LED5が点灯→消灯→点灯…するプログラムを作成せよ。割り込みを使用したプログラムにすること。
<ヒント>SW5を押しながらSW4を押しても反応しないこと。どこで割り込みを発生するのが良いか考える。