2002/06/03 【ソフトウエア編TOPに戻る】
前回はCMT(コンペアマッチタイマ)を使って、簡単な時間潰しの処理をご紹介しました。今回はCMTを使って一定時間ごとに割り込みを発生させ、メインの処理とは別のお仕事をさせてみます。つまり、インターバルタイマー動作ということになります。インターバルタイマーは、一定間隔ごとに何かを監視しにいったり制御しにいったりということを、メインとなるプログラム処理に負担を掛けずに、しかも正確な時間間隔で起動することができます。
割り込み処理の考え方に関しては、H8のページ105.タイマー割込みで効率的な制御でもご紹介していますので、そちらもご参照ください。
212−1.SH2での割り込み処理
CMTの基本動作については、前回の211.CMTで時間つぶし(SH2-7045F)を見て頂くとして、ここでは前回触れなかったCMTでの割り込み発生の処理について、簡単にご紹介します。
CMTは、CMCSR(コンペアマッチタイマコントロール/ステータスレジスタ)のCMIE(コンペアマッチ割り込みイネーブル)ビットを1にしておくと、コンペアマッチしたことによって割り込みを発生させることができます。SH2の場合、割り込みが発生した時に行なわれる処理はそれなりに難しい手順を辿りますが、大分省略して簡単に言うと以下のような感じになります。
例)CMT1のコンペアマッチが割り込みが発生した場合。
■1.CMT1のCMCNT(コンペアマッチカウンタ)がCMCOR(コンペアマッチコンスタントレジスタ)の値と一致すると、コンペアマッチ状態になる。
■2.CMCSR(コンペアマッチタイマコントロール/ステータスレジスタ)のCMIE(コンペアマッチ割り込みイネーブル)ビットが1だったら、割り込みコントローラに対して割り込み要求を出す。
■3.CMT1の割込み優先レベルが、CPUのSR(ステータスレジスタ)に設定されている割込みマスクレベルより高ければ、割込みが実行される。同じか、低ければ、割り込み要求は無視される。
■4.割り込み優先レベルが、割込みマスクレベルより高かった場合、SRやPC(プログラムカウンタ)、そしてメインの処理で必要なレジスタの内容等をスタックエリアに退避し、CMT1用に決められた割り込みベクタアドレス(H'00000250)に書き込まれているアドレスにジャンプする。つまり、所定の割り込みベクタアドレスに割り込み処理プログラムの先頭アドレスを書き込んでおくことによって、割込みプログラムが起動するということになる。
■5.割り込みプログラムが終了したら、スタックに退避していたSRやPC、そして必要なレジスタの内容などを元に戻して、メインの処理に戻る。
ここで耳慣れないのが、割込み優先レベルと割り込みマスクレベルという言葉です。一体なんでしょう?
【割り込み優先レベル】
SH2-7045Fでは、割り込みを発生さえる要因として、NMIおよびIRQ0〜7の外部要因が9個、内蔵周辺機能からの内部要因が43個と、多くの割り込み要因があります。これらの割込み要因は、プログラムの目的によって優先順位を設定することができます。優先順位の設定に際しては、各々レベル0からレベル15までの16段階に設定することができ、その中ではレベル15が最も高い優先順位になります。
複数の割込み要因が同じレベルに設定されている場合に、それらの割込みが同時に発生した時には、予め決めらている優先順位に従って割り込み処理が実行されます。詳しくはSH2のハードウエアマニュアルの「割込みコントローラ」の項目を参照して下さい。
割り込み要因で一番優先順位が高いのがNMI(ノンマスカブル割込み)で、レベル16で固定です。次に高いのがデバッグツールなどで利用するユーザーブレイク割り込みで、レベル15で固定です。それ以外の割込み要因については、レベル0から15の間で、自由に設定することができます。
割り込み要因の優先レベル設定は、IPRA〜IPRHの8個の16ビット幅の割込み優先レベル設定レジスタに設定値を書き込むことによって行ないます。
| レジスタ | ビット | |||
| 15〜12 | 11〜8 | 7〜4 | 3〜0 | |
| IPRA | IRQ0 | IRQ1 | IRQ2 | IRQ3 |
| IPRB | IRQ4 | IRQ5 | IRQ6 | IRQ7 |
| IPRC | DMAC0 | DMAC1 | DMAC2 | DMAC3 |
| IPRD | MTU0 | MTU0 | MTU1 | MTU1 |
| IPRE | MTU2 | MTU2 | MTU3 | MTU3 |
| IPRF | MTU4 | MTU4 | SCI0 | SCI1 |
| IPRG | A/D | DTC | CMT0 | CMT1 |
| IPRH | WDT,BSC | I/O | 予約 | 予約 |
各々の割り込み優先レベル設定レジスタは、4ビットずつに意味付けが区切られており、それぞれが個別の割り込み発生要因に割り当てられています。そして4ビットなので0〜15までの設定値を書き込むことができます。つまり、好きな割込み要因に相当する部分の4ビットに、0〜15の設定値を書き込むことによって、それぞれの割り込み優先レベルが設定できるということになります。
例えば、CMT1の割込み優先レベルを7にしたければ、IPRGレジスタの3〜0ビットに7(0111)を書き込めばよいということになります。
【割り込みマスクレベル】
ある割込み処理を実行している最中に他の割込みが発生した場合はどうなるのでしょう。今実行中の割り込み処理よりも、優先レベルが高い割り込みが発生したらそちらを実行させ、優先レベルが低い割り込みが発生しても無視する、といったことが必要になるでしょう。それを行なうのが割り込みマスクです。
CPUのSR(ステータスレジスタ)の7〜4番目のビットは割り込みマスクビット(I3〜I0)として割り当てられています。プログラムの実行中に割り込みが発生した場合、発生した割り込みの優先レベルがこの割込みマスクビットにコピーされます。この時、別の割込みが同時に発生した場合、その割込みの優先レベルが、割込みマスクビットに設定されているレベルと比較され、高ければ割込みが実行され、同じか低ければ無視されます。つまり、割込みマスクビットに設定されているレベルによって、プログラム全体の割込みの実行が管理されているのです。
【忘れずに!】
割り込み処理を使うプログラムを作る時には、必ず割り込みマスクビットの設定と、割込み要因の優先レベル設定を行なう必要があります。なにもしないままだと両者ともレベル0に設定され、割り込み処理が実行されません。割り込みマスクレベルを0にしたら、発生させたい割り込み要因の優先レベルは、少なくとも0を超える値の1以上に設定しなければなりません。
212−2.CMT1の割込みプログラム例 cmttest2.c
では、CMT1(コンペアマッチタイマ1チャンネル)を使った割り込みプログラムの例をご紹介しましょう。今回のプログラムでは、前回ご紹介したCMT0を使った時間潰しプログラムに割り込みを掛ける形の動作とします。メインプログラムではCMT0のwait( )関数で1秒間ごとにPD16〜23のカウントアップを行なう動作をさせ、それと平行してCMT1で0.5秒ごとに割り込みを発生させ、PD0〜7のカウントアップを行ないます。なお、割込みを利用するため、割り込みベクタテーブルで割り込み関数の宣言も行なっています。
プログラムはここをクリックするとダウンロードできます。cmttest2.c また、割込みベクタファイルもここをクリックするとダウンロードできます。vector2.s 割込みベクタをダウンロードしたら、必ずvector.sという名前に変更してからご利用下さい。
なお、下のプログラムソースでは、前回のプログラムcmttest1.cに付け加えている部分だけを青で表示しています。
|
/********************************************************** cmttest2.c 2002.05.26 RIKIYA SH2_7045F CMT1を使って0.5秒単位の割り込み処理を行なう。 main()関数では、PD16〜PD23BITを1秒ごとにカウントアップ。 CMT1割込みint_cmi1()関数ではPD0〜7BITを0.5秒ごとにカウントアップ /*********************************************************/ #include "sh7040s.h" void boot(void); void __main(void); int main(void); void sectionInit(void); void wait(unsigned short); void int_cmi1(void); void set_imask( int ); /* __main(ダミー) *****************************************/ void __main(void){ } /*CPU初期化**********************************************/ void boot(void){ /* IOポート設定 */ PFC.PACRH.WORD = 0x0000; /* PA23〜16 */ PFC.PACRL1.WORD = 0x0000; /* PA15〜8 */ PFC.PACRL2.WORD = 0x0145; /* PA7,6,5,2 TXD0,RXD0 */ PFC.PBCR1.WORD = 0x0000; /* PB9,PB8 */ PFC.PBCR2.WORD = 0x0000; /* PB7〜PB0 */ PFC.PCCR.WORD = 0x0000; /* PC15〜PC0 */ PFC.PDCRH1.WORD = 0x0000; /* PD31〜PD24 */ PFC.PDCRH2.WORD = 0x0000; /* PD23〜PD16 */ PFC.PDCRL.WORD = 0x0000; /* PD15〜PD0 */ PFC.PECR1.WORD = 0x0000; /* PE15〜PE8 */ PFC.PECR2.WORD = 0x0000; /* PE7〜PE0 */ PFC.PAIORH.WORD = 0x00ff; /* [OUT]PA23〜16 [IN]-- */ PFC.PAIORL.WORD = 0xffff; /* [OUT]PA15〜0 [IN]-- */ PFC.PBIOR.WORD = 0x03ff; /* [OUT]PB7 〜0 [IN]-- */ PFC.PCIOR.WORD = 0xffff; /* [OUT]PC15〜0 [IN]-- */ PFC.PDIORH.WORD = 0x00ff; /* [OUT]PD23〜16 [IN]PD31〜24 */ PFC.PDIORL.WORD = 0xffff; /* [OUT]PD15〜0 [IN]-- */ PFC.PEIOR.WORD = 0xffff; /* [OUT]PE15〜0 [IN]-- */ /* CMT設定 */ CMT0.CMCSR.BIT.CKS = 0; /* CMT0 CLOCK/8 */ CMT0.CMCOR = 3579 ; /* 1mS/0.279uS = 3579 */ CMT.CMSTR.BIT.STR0 = 0; /* CMT0 STOP */ INTC.IPRG.WORD |= 0x0001; /* CMT1 割込みLEVEL = 1 */ CMT1.CMCSR.BIT.CKS = 3; /* CMT1 CLOCK/512 */ CMT1.CMCSR.BIT.CMIE = 1; /* CMT1 割り込み許可 */ CMT1.CMCOR = 27964; /* 0.5S/17.88uS = 27964 */ CMT.CMSTR.BIT.STR1 = 0; /* CMT1 STOP */ sectionInit(); /* メモリ初期化 */ main(); /* メイン関数呼び出し */ } /* メイン関数********************************************/ int main(void){ unsigned char i=0; set_imask(0); /* 割り込みマスク解除 */ PD.DR.BYTE.LL = 0xAA; CMT.CMSTR.BIT.STR1 = 1; /* CMT1 START */ for(;;){ i++; PD.DR.BYTE.HL = i; /* PD16〜23 カウントアップ */ wait(1000); /* 1000mS待つ */ } return 0; } /* CMT0 時間稼ぎ******************************************* 引数のmsecには、1/1000秒単位で待機時間を指定する。 unsigned short型、0xffff(65535)まで。 ***********************************************************/ void wait(unsigned short msec){ unsigned short i; CMT.CMSTR.BIT.STR0 = 1; /* CMT0 START */ for(i=0;i<msec;i++){ do{ /* CMCNT = CMCORまで待つ(1s)*/ }while(CMT0.CMCSR.BIT.CMF == 0); CMT0.CMCSR.BIT.CMF = 0; /* コンペアフラグをリセット */ } CMT.CMSTR.BIT.STR0 = 0; /* CMT0 STOP */ return 0; } /* CMT1 0.5秒ごとの割り込みインターバルタイマ **************/ #pragma interrupt void int_cmi1(void){ static char CNT; CNT++; /* カウンタ変数をアップ */ PD.DR.BYTE.LL = CNT; /* PD0〜PD7ビットに出力 */ CMT1.CMCSR.BIT.CMF = 0; /* コンペアフラグをリセット */ } /* 割り込みマスクレベル設定 ******************************/ void set_imask( int imask ) { int srreg; imask <<= 4; imask &= 0x00f0; asm(" stc sr,%0 ":"=r"(srreg):); srreg &= 0xffffff0f; srreg = srreg | imask; asm(" ldc %0,sr "::"r"(srreg)); } /* セクション初期化 ************************************/ extern char etext, sdata, edata, bss_start, end; void sectionInit(void){ char *src; char *dst; /* 初期化データ領域 */ src = &etext; dst = &sdata; while (dst < &edata) { *dst++ = *src++; } /* 未初期化データ領域 */ for ( dst = &bss_start; dst < &end; dst++ ) { *dst = 0; } } |
割り込みベクタvector2.s(抜粋)
|
/******************************************* ベクタテーブル 2002.05.26 GCC SH2-7045F CMT1割り込み _int_cmi1 *******************************************/ .text .extern _start,_stack .extern _int_cmi1 .section .vector .long _start /* パワーONリセット */ 【 中 略 】 .long _start
/* [144]コンペアマッチタイマ0 */ 【 中 略 】 .end |
212−3.プログラムの説明
今回のプログラムは、前回の211.CMTで時間つぶし(SH2-7045F)でご紹介したプログラムcmttest1.cに、割込み処理だけを付け足したものです。なので、付け足した部分を中心に解説することにします。初めての方は、是非前回の解説記事をご覧下さい。
■使用関数の型宣言
void int_cmi1(void);
void set_imask( int );
ここでは、int_cmi1( )関数とset_imask( )関数の型宣言を追加で行なっています。int_cmi1( )関数は、CMT1で割り込みが発生した場合に実行される関数で、set_imask( )関数は、SR(ステータスレジスタ)の割込みマスクビットに、割込みマスクレベルを設定するための関数です。
■CMT1チャンネルの設定
INTC.IPRG.WORD |= 0x0001; /* CMT1 割込みLEVEL = 1 */
CMT1.CMCSR.BIT.CKS = 3; /* CMT1 CLOCK/512 */
CMT1.CMCSR.BIT.CMIE = 1; /* CMT1 割り込み許可 */
CMT1.CMCOR = 27964; /* 0.5S/17.88uS = 27964 */
CMT.CMSTR.BIT.STR1 = 0; /* CMT1 STOP */
ここでは、CMT1の設定を行なっています。
INTC.IPRG.WORD |= 0x0001; /* CMT1 割込みLEVEL = 1 */
では、割り込みレベル設定レジスタIPRGの3〜0ビットに対して1を設定しています。ここの4ビットはCMT1の割込みレベル設定に割り付けられているので、CMT1の割込みレベルを1に設定したことになります。|= は、INTC.IPRG.WORD = INTC.IPRG.WORD | 0x0001; と同じ意味で、IPRGレジスタの3〜0ビット目を強制的に(ORで)1にしたことになります。
CMT1.CMCSR.BIT.CKS = 3; /* CMT1 CLOCK/512 */
では、CMT1のカウントクロックを、システムクロックの1/512に設定しています。
CMT1.CMCSR.BIT.CMIE = 1; /* CMT1 割り込み許可 */
では、CMT1の割込み許可ビットを1にして、割り込み発生を許可しています。
CMT1.CMCOR = 27964; /* 0.5S/17.88uS = 27964 */
では、コンペアマッチを発生させるカウント値を27964に設定しています。今回利用するボードのシステムクロックは、x4倍動作で28.63636MHzです。今回カウントクロックを1/512に設定したので、ひとつカウントアップするのに17.8793μS掛かります。今回、0.5秒ごとにコンペアマッチ割込みを発生させたいので、0.5/17.8793uS = 27964 までカウントすると0.5秒ということになります。
CMT.CMSTR.BIT.STR1 = 0; /* CMT1 STOP */
最後に、CMT1のカウンタをひとまず停止させておきます。
■メイン関数
int main(void){
unsigned char i=0;
set_imask(0); /* 割り込みマスク解除 */
PD.DR.BYTE.LL = 0xAA;
CMT.CMSTR.BIT.STR1 = 1; /* CMT1 START */
for(;;){
i++;
PD.DR.BYTE.HL = i; /* PD16〜23 カウントアップ */
wait(1000); /* 1000mS待つ */
}
return 0;
}
メイン関数では、
set_imask(0); /* 割り込みマスク解除 */
PD.DR.BYTE.LL = 0xAA;
CMT.CMSTR.BIT.STR1 = 1; /* CMT1 START */
の3行を追加しています。それ以外は変わらず、PD16〜23ビットで1秒ごとにカウントアップ動作を行なう処理が記述されているだけです。
set_imask(0); /* 割り込みマスク解除 */
では、SRの割込みマスクビットのレベルを0(ゼロ)に設定しています。この関数の中身については後でご紹介します。先ほど、INTC.IPRG.WORD |= 0x0001; でCMT1の割込み優先レベルを1に設定したので、例えばここでset_imask(1); などと設定すると、今回の割込みプログラムは実行されないことになります。
PD.DR.BYTE.LL = 0xAA;
は、デバッグ的な意味合いで入れてあります。今回、割込み処理内でPD0〜7ビットを0.5秒ごとにカウントアップさせますが、割り込み処理が実行されなければ、この命令文によっていつまでたってもPD0〜7ビットは0xAA(1010 1010)を出力したままとなります。
CMT.CMSTR.BIT.STR1 = 1; /* CMT1 START */
で、CMT1のカウントアップを開始させています。いよいよ0.5秒ごとの割込みを発生させるためのカウントアップが始まりました。
■CMT1割込み関数 int_cmi1( )
#pragma interrupt
void int_cmi1(void){
static char CNT;
CNT++; /* カウンタ変数をアップ */
PD.DR.BYTE.LL = CNT; /* PD0〜PD7ビットに出力 */
CMT1.CMCSR.BIT.CMF = 0; /* コンペアフラグをリセット */
}
この部分が、CMT1のコンペアマッチによって実行される割り込み関数になります。割り込み関数名のint_cmi1は、割り込みベクタテーブルで指定しているラベル名に合わせてあります。
#pragma interrupt
は、割り込み関数であることを宣言しています。
static char CNT;
は、静的メモリ領域にCNTというchar型(8ビット幅)の変数を確保しています。
CNT++; /* カウンタ変数をアップ */
PD.DR.BYTE.LL = CNT; /* PD0〜PD7ビットに出力 */
CMT1.CMCSR.BIT.CMF = 0; /* コンペアフラグをリセット */
では、CNTにプラス1して、PD0〜7ビットにCNTの値を出力しています。その後、CMT1のCMF(コンペアマッチフラグ)をゼロクリアして、次の割込み処理に備えます。つまり、この割込み関数が呼ばれるたびに、CNTが1ずつ増えて、PD0〜7がカウントアップされて行きます。
■割込みマスクレベル設定関数 set_imask( )
void set_imask( int imask )
{
int srreg;
imask <<= 4;
imask &= 0x00f0;
asm(" stc sr,%0 ":"=r"(srreg):);
srreg &= 0xffffff0f;
srreg = srreg | imask;
asm(" ldc %0,sr "::"r"(srreg));
}
この関数は、アルファプロジェクト製SH2マイコンボードに付属のサンプルプログラムに記述されていたまんまのものです。(アルファプロジェクトさんすみません)
ここでは、SR(ステータスレジスタ)の7〜4ビット目にある割り込みマスクビットに対して、引数のimaskの値を書き込む処理が行なわれています。アセンブラ命令を交えた記述です。(力弥は勉強不足のため、詳しくは解説できませんが...)
■割込みベクタテーブル vector2.s
.text
【 中 略 】
.long _start /* [144]コンペアマッチタイマ0 */
.space 4*3
.long _int_cmi1
/* [148]コンペアマッチタイマ1 */
【 中 略 】
.end
割り込み処理を行なう場合は、割込みベクタテーブルの修正をする必要があります。
.extern _int_cmi1
では、割り込みベクタテーブルに、実行させたい割り込み関数の関数名(ラベル名)を宣言しています。
.long _int_cmi1 /* [148]コンペアマッチタイマ1 */
では、該当する割込みベクタアドレスの部分に、関数名(ラベル名)を記述しておきます。
212−4.プログラムの動作
![]() |
プログラムをダウンロードしてSH2マイコンボードの電源を入れると、PD16〜23ビットに接続した8ビットLEDが1秒ごとにカウントアップを開始します。同時に、外部に接続したPD0〜7ビットのLEDランプは、その2倍の速さ0.5秒ごとにカウントアップ動作を行ないます。
wait( )関数はまるっきりの時間潰し関数で、時間を潰している間の1秒間は他に何も処理できないのですが、割込みを使うことで、別の処理を行なうことが可能になるという事例です。
|
これで、ひとまずは最も簡単なタイマーカウンタCMTの機能については終了です。SH2に特有のプログラミング上のクセなどにも、少しずでも慣れてもらえたのではないかと思いますが、いかがでしょう? これからもこんな調子で少しずつ機能の紹介をして行きたいと思います。とりあえず次回はMTU(マルチタイマーパルスユニット)を使ったPWM波形の出力実験などについて進んでいきたいと思います。