CMTの割り込み
ここでは、SH7085内蔵のCMTモジュールの割り込みについて学習する。
CMT割り込み
CMT(Compare Match Timer)は、SH7085内蔵モジュールの一つで、16ビットのカウンタを持ち、選択したクロックによりカウントアップする。
設定した周期ごとに割り込みを発生するようにプログラムすることが可能であり、この機能を使用すると、正確な周期で割り込みが発生するため、時間の管理が簡単にできるようになる。
CMTの基本的な設定は「CMTの使い方」のテキストを参照してほしい。ここでは、割り込みに関することを主に説明する。
割り込み関連のレジスタ
CMCSR(コンペアマッチタイマコントロール/ステータスレジスタ)
CMTに供給するクロックの選択(CKS)や、コンペアマッチの発生を示すフラグ(CMF)が存在するCMCSRレジスタに、コンペアマッチ割り込みを許可/禁止するビット(CMIE)がある。
このビットを1にすることにより、コンペアマッチが発生した時に割り込み要求を発生することができる。(CMFフラグが1になった時に、割り込み要求が発生する)
IPRJ(インタラプトプライオリティレジスタJ)
CMTが発行する割り込み要因の、優先順位(レベル0~15)を設定する。
CMTのチャネル0は_CMT0を、チャネル1は_CMT1を使用する。優先レベルは4ビットで設定し、その範囲は0~15である。
優先レベルが0の時、割り込みは発生しなくなる。
CMT割り込み発生の具体的手順
CMT割り込みを使用した、簡単なプログラムを用意したので
int2.zip
ダウンロードし、プロジェクトint2_1をビルド、実行してみる。
一定周期でLED6が点滅するプログラムであり、初期状態では、割り込みを使わないプログラムになっている。
main()関数のwhile()ループの中で、CMT0のコンペアマッチフラグ(CMF)を監視して、LED6の操作をしている。
1: void main() 2: { 3: PFC.PEIORL.BIT.B11 = 1; // PE11(LED6)端子を出力モードに設定 4: 5: STB.CR4.BIT._CMT = 0; // CMTモジュールスタンバイの解除 6: CMT0.CMCSR.BIT.CKS = 3; // 1/512 7: CMT0.CMCOR = 19531 - 1; // 500ms 8: 9: LED6 = LED_OFF; 10: CMT.CMSTR.BIT.STR0 = 1; // CMT0スタート 11: while (1) { 12: if (CMT0.CMCSR.BIT.CMF) { 13: CMT0.CMCSR.BIT.CMF = 0; 14: LED6 ^= 1; 15: } 16: } 17:}
5行目で、CMTのモジュールスタンバイを解除している。電源投入後の初期状態では、CMTモジュールへはクロックが供給されずに、スタンバイモードになっている。
6,7行目で、CMTのコンペアマッチ周期を設定している。Pφ=20.0MHz、分周比1/512であるから、カウントする周波数は
であり、周期は25.6[us]である。
これを19531カウントしているので、周期は≒500[ms]である。なお、CMCORへ設定する値は-1すること。
9行目では、LEDの初期状態を設定している。
10行目でCMTのカウントをスタートしている。
11行目からのwhileループの中で、CMTのコンペアマッチフラグを調べ、フラグが立っていればフラグのクリアと、LED6の点滅処理を行っている。
以上が、割り込みを使わない場合のプログラムである。
次に、プログラム先頭のほうにある#define USE_INTのコメントアウトを外して、ビルド、実行する。今度は割り込みを使うプログラムである。
割り込みを使わない場合と、同じ動作になっていることを確認する。
1:void main() 2:{ 3: PFC.PEIORL.BIT.B11 = 1; // PE11(LED6)端子を出力モードに設定 4: 5: STB.CR4.BIT._CMT = 0; // CMTモジュールスタンバイの解除 6: CMT0.CMCSR.BIT.CKS = 3; // 1/512 7: CMT0.CMCOR = 19531 - 1; // 500ms 8: CMT0.CMCSR.BIT.CMIE = 1; // コンペアマッチ割り込み許可 9: INTC.IPRJ.BIT._CMT0 = 8; // 割り込み優先レベル = 8 10: 11: set_imask(7); // マスクビット = 7 12: 13: LED6 = LED_OFF; // LED6初期状態 14: CMT.CMSTR.BIT.STR0 = 1; // CMT0スタート 15: while (1) 16: ; 17:}
7行目までは、割り込みを使わないプログラムと同じである。
8行目で、CMT0のコンペアマッチが発生した時に割り込み要求をするように設定している。
9行目は、CMT0のコンペアマッチ割り込みの、割り込み優先レベルを8に設定している。
11行目は、CPUのマスクビットを7に設定している。これにより、優先レベル8以上の割り込みを受け付けるようになる。
13行目以降は、LED6を初期状態にし、CMT0をスタートしている。whileループの中は何もしていない。
LEDの点滅処理は、CMTのコンペアマッチが発生するたびに呼び出される関数INT_CMT0_CMI0()の中で行っている。
1:#pragma interrupt INT_CMT0_CMI0 2:void INT_CMT0_CMI0() 3:{ 4: CMT0.CMCSR.BIT.CMF = 0; // 割り込み要求をクリア 5: 6: LED6 ^= 1; 7:}
1行目の#pragma interrupt INT_CMT0_CMI0は、INT_CMT0_CMI0という関数を、割り込み処理のための関数と識別する特殊命令である。
4行目は、コンペアマッチフラグ(CMF)をクリアしている。CMF=1になるときに割り込み要求が発生し、CMFは自動的にクリアされることがないため、ソフトウェアでクリアしなければならない。(クリアしないと、二度目の割り込みが発生しない。試してみると良い)
6行目は、LED6の状態を切り替えている。
以上が割り込みを使用した場合のプログラムの説明である。
本当に割り込みで動いているのかどうか、割り込み優先レベルや、マスクビットの設定を変更して確かめてみるのも良い。
割り込みの効用
ここで、割り込みを使用することによる利点について考えてみる。
例えば、入力としてスイッチが50個あり、LEDを制御しながら、モータも制御するというプログラムを考える。スイッチ入力に割り込みを使用しないプログラムは次のようになるであろう。
main() { : while(1) { if (SW1 == 1) { : } if (SW2 == 1) { : } if (SW3 == 1) { : } : : if (SW50 == 1) { : } LED6 = LED_ON: for (i = 0; i < 10000; i++) ; LED6 = LED_OFF; morot_ctrl(); } }
メイン関数の無限ループ内で、スイッチを監視し、スイッチ関連の処理を行っている。ループが一周するたびに、LEDの処理をし、さらにモータの制御も行っている。
動作に問題は無いように見えるが、大きな欠点がある。各スイッチはループが一周するたびに一度しか読み込まれていない。そのため、for()ループを実行中はスイッチは全く読み込まれない。たとえスイッチのうち一つが非常停止ボタンであったとしても、for()ループ中は無視されてしまう。
では、for()ループの中で重要なスイッチを監視すれば良いのでは、という考えもある。しかし、motor_ctrl()関数内のループはどうなのか、など複雑なプログラムになると、全てのループ内にスイッチ監視処理を埋め込むことは困難である。
このような例では、スイッチの反応時間が一定でなくなる(反応が鈍い)、というのが大きな問題である。
このような問題は割り込みを使うと、解決することができる。
#pragma interrupt INT_IRQ0 void INT_IRQ0() { : } #pragma interrupt INT_IRQ1 void INT_IRQ1() { : } #pragma interrupt INT_CMT0_CMI0 void INT_CMT0_CMI0() { // LEDの点灯/消灯処理 : } main() { while (1) { morot_ctrl(); } }
メイン関数のループは、重要な処理(motor_ctrl())に専念し、スイッチが押されれば割り込み関数内で処理を行う。
時間のかかっていたfor()ループは、CMTによる割り込み処理に置き換えた。
非常停止ボタンは、割り込み優先レベルを高くし、優先的に割り込み処理を受け付けるようにすることもできる。
このように割り込みを上手く使うことにより、信頼性の高いプログラムを、見通し良く書くことができる。
演習問題
1.Pφ=20.0MHz、分周比1/8の時、CMTでカウントできる時間の最大値はいくつか。
2.プログラムint2_1を改造し、LED5とLED6が交互に点滅を繰り返すプログラムを作成せよ。
3.プログラムint2_1を改造し、SW4を押したときにLED5が0.5秒間だけ点灯するプログラムを作成せよ。
※IRQ0(SW4)の割り込み関数名はINT_IRQ0とし、プロジェクトを構成している”intprg.c”の138行目を、次のようにコメントアウトしてからビルドする。
//void INT_IRQ0(void){/* sleep(); */}
4.プログラムint2_2は、CMTの割り込みを使用しスピーカーから1kHzの音を出し続けるプログラムであるが、未完成である。これを正しく動くようにせよ。
5.プログラムint2_2を改造し、SW4を押したときにスピーカーから1kHzの音が0.5秒間だけ出力されるプログラムを作成せよ。
<ヒント>
- CMT0とCMT1を使用する。CMT0は1kHzの音を作るのに使用し、CMT1は0.5秒を作るために使用する。
- SW4の割り込み内で、CMT0とCMT1を起動する。CMT0の割り込み関数はSPK端子の反転を行い、CMT1の割り込み関数ではCMT0とCMT1を停止する。
※CMT1の割り込み関数名はINT_CMT1_CMI1とし、プロジェクトを構成している”intprg.c”の386行目を、次のようにコメントアウトしてからビルドする。
//void INT_CMT1_CMI1(void){/* sleep(); */}
6.プロジェクトint2_3は、7セグメントLEDのダイナミック点灯をCMT割り込みで実現しようとしているが、未完成である。これを正しく動くようにせよ。
7.プログラムint2_3を改造し、100msごとに7セグメントLEDの数字がカウントアップするプログラムを作成せよ。
8.プログラムint2_3を改造し、SW4を押すたびに7セグメントLEDの数字がカウントアップするプログラムを作成せよ。
※IRQ0(SW4)の割り込み関数名はINT_IRQ0とし、プロジェクトを構成している”intprg.c”の138行目を、次のようにコメントアウトしてからビルドする。
//void INT_IRQ0(void){/* sleep(); */}