内容
R8C/M12Aの内蔵周辺機能のひとつであるタイマRB2を使って、インターバル割り込みを発生させます。今回は、1msごとに割り込みを発生させ(インターバルタイマ)、時間を正確に計ります。
今回の「タイマRB2」の「2」は、Ver.2という意味です。型式の違うR8CマイコンにはタイマRBがありますが、タイマRB2の方が機能がアップしています!
回路、ブレッドボード実装図は、LEDの点灯(I/Oポートの出力)と同じです。
ワークスペース
r8cm12a_led_interrupt_100.zip
ファイルを解凍し、フォルダを「c:\worksapce」に入れてください。
プログラムの説明
タイマRB2の設定
タイマRB2の概要(タイマモード)を、下図に示します。
タイマRB2を使って、定期的に割り込みを発生させます。割り込み先のプログラムは、設定した時間ごと実行されるので、割り込みプログラムを実行した回数を数えると(変数を+1すると)、正確に時間を計ることができます。今回は、1msごとに割り込みを発生させることにします。
これからタイマRB2のタイマモードについて、説明します。
■タイマRBモードレジスタ(TRBMR)の設定
bit |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
設定 内容 |
0 |
タイマRBカウントソース 選択ビット 000:f1(20MHz) 001:f8(2.5MHz) 010:タイマRJ2のアンダフロー 011:f2(10MHz) 100:f4(5MHz) 101:f32(0.625MHz) 110:f64(0.3125MHz) 111:f128(0.15625MHz)
|
0 |
0 |
タイマRB動作モード 選択ビット 00:タイマモード 01:プログラマブル波形発生モード 10:プログラマブルワンショット発生モード 11:プログラマブルウェイトワンショット発生 |
設定値 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
|
タイマRBカウントソース選択ビットは、f1(20MHz)を選びます。これは、タイマRBプリスケーラレジスタ(TRBPRE)の値が、50ns(1/20MHz)ごとにカウントダウンしていくことになります。
タイマRB動作モード選択ビットは、今回は「タイマモード」を選びます。
■タイマRBプリスケーラレジスタ(TRBPRE)の設定
■タイマRBプライマリレジスタ(TRBPR)の設定
bit |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
設定 内容 |
割り込みをかける間隔を設定 |
設定値 |
TRBPRE=200−1 TRBPR=100−1 |
|
概要の図のとおり、タイマRBモードレジスタ(TRBMR)のタイマRBカウントソース選択ビットで設定したクロックで、タイマRBプリスケーラレジスタ(TRBPRE)の値が1ずつ減っていきます。今回は、50nsごとに値が減っていきます。値が、「・・・→1→0→199(今回の設定値)→198→・・・」と、0から199に変わる瞬間にアンダーフローで、1パルス出力されます。今回は50ns×200(タイマRBプリスケーラレジスタ(TRBPRE)に設定した値)=10μsごとに1パルス出力されます。出力先は、タイマRBプライマリレジスタ(TRBPR)で、こちらも1パルスごとに値が1ずつ減っていきます。値が、「・・・→1→0→99(今回の設定値)→98→・・・」と、0から99に変わる瞬間にアンダーフローで割り込み信号が出力され、タイマRB割り込みプログラムが実行されます。今回は、10μs×100(タイマRBプライマリレジスタ(TRBPR)に設定した値)=1msごとに割り込みが発生することになります。
結論は、次の計算式で割り込み間隔が決まります。
割り込み間隔
=タイマRBカウントソースで選択したクロック×TRBPRE×TRBPR
=50ns×TRBPRE×TRBPR
今回、割り込み間隔は1msにします。よって、
1ms=50ns×TRBPRE×TRBPR
TRBPRE×TRBPR=20000
※ただし、TRBPREとTRBPRは、256以下の値
よって、TRBPREとTRBPRをかけたときに、20000になる値を探します。値を決める法則はありませんので、20000になる値を各自で見つけてください。
今回は、TRBPREは200、TRBPRは100とします。
値の設定は、1引いた値を設定します。したがって、プログラムでは、TRBPRE=199、TRBPR=99を設定します。これは、例えばTRBPREの場合、「199→198→・・・→1→0→199→・・・」というように、0も含めてカウントダウンするためです。199から0までの個数が200個ということです。人が数えるときは、0になった瞬間で終わりですが、マイコンは0も含め、0から違う値に変わる瞬間がそのタイミングになることが多いので覚えておきましょう。
■タイマRB割り込み制御レジスタ(TRBIR)の設定
bit |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
設定 内容 |
タイマRB割り込み 許可ビット 0:割り込み禁止
1:割り込み許可 |
タイマRB割り込み 要求フラグ 0:割り込み要求なし 1:割り込み要求あり |
0 |
0 |
0 |
0 |
0 |
0 |
設定値 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
ビットの 名称 |
TRBIF_TRBIR |
|
|
|
|
|
|
|
|
タイマRBプライマリレジスタ(TRBPR)の値が「→0→99(今回の設定値)」と、0から99に変わる瞬間に割り込み信号が出力されますが、タイマRB割り込み許可ビットで割り込みを発生させるかを選択します。今回は割り込みを発生させるので"1"にします。
■割り込み優先レベルレジスタC(ILVLC)
bit |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
設定 内容 |
0 |
0 |
INT1割り込み優先 レベル設定ビット
今回は設定しません |
0 |
0 |
タイマRB2割り込み優先 レベル設定ビット 00:レベル0 (割り込み禁止) 01:レベル1 10:レベル2 11:レベル2 |
設定値 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
|
タイマRB2割り込み優先レベルを設定します。これは他の割り込みが同時に起こった場合、どの割り込みを優先するかを決める設定です。今回はタイマRB2の割り込みしか使っていないのでレベル1〜3のどれを設定しても変わりませんが、一応一番大きい3を設定しておきます。
■タイマRB制御レジスタ(TRBCR)
bit |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
設定 内容 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
タイマRBカウント開始ビット 0:カウント停止
1:カウント開始 |
設定値 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
ビットの 名称 |
|
|
|
|
|
|
|
TSTART_TRBCR |
|
概要の図にある「TSTART_TRBCR」のスイッチ部分が初期状態ではOFFなので、タイマRBカウント開始ビットを"1"にして、このスイッチをONにします。
■全体の割り込みを許可する
タイマRB割り込み制御レジスタ(TRBIR)で、タイマRB2の割り込みを許可しましたが、初期状態ではマイコン全体の割り込みが禁止になっています。
そのため、最後にマイコン全体の割り込みを許可します。全体割り込み許可は、マイコンのCPUレジスタという、C言語では操作できない場所にあるので、下記の様にアセンブラで命令を記述します。asm関数は、C言語プログラム内でアセンブラ命令を記述する命令です。
/* 全体割り込みの許可 */
asm(" fset I "); /* 全体の割り込み許可 */
タイマRB2の設定は、init関数内で行います。プログラムを下記に示します。
/* タイマRBの設定 1msごとに割り込みを発生させる*/
msttrb = 0; /* タイマRB2を有効にする */
trbmr = 0x00; /* 動作モード、分周比設定 */
trbpre = 200-1; /* プリスケーラレジスタ */
trbpr = 100-1; /* プライマリレジスタ */
trbir = 0xc0; /* タイマ割込み要求 */
ilvlc = 0x03; /* 割込み優先レベル設定 */
trbcr = 0x01; /* カウント開始 */
/* 全体割り込みの許可 */
asm(" fset I "); /* 全体の割り込み許可 */
「msttrb」は、タイマRB2 スタンバイビットで、タイマRB2を有効にするか、無効にするかを選択するビットです。0で有効です。初期状態ではタイマRB2は無効になっているので、タイマRB2を使う前にこのビットを0にしてください。
割り込みプログラム
割り込みプログラムを下記に示します。今回はこのプログラムが1msごとに実行されます。
#pragma interrupt intTRB(vect=24)
void intTRB( void )
{
trbif_trbir = 0; /* フラグのクリア */
cnt_rb++;
}
intTRB関数が、タイマRB2割り込みが発生したときに実行される関数です。この関数がタイマRB2割り込みの実行先ということを設定するのが、「#pragma interrupt」命令です。この命令の書式を下記に示します。
#pragma interrupt 割り込み関数名(vect=割り込み番号)
void 割り込み関数名( void )
{
割り込みプログラム
}
タイマRB2の割り込み番号は24と決まっています。どの内蔵周辺機能が何番の割り込み番号かは、ルネサス エレクトロニクスの「R8C/M11Aグループ、R8C/M12Aグループ ユーザーズマニュアル ハードウェア編」の「可変ベクタテーブル」部分を参照してください。
割り込み関数名は、自由に付けて構いません。今回はタイマRB2割り込みなので、割り込みの「interrupt」とタイマRB2の「Timer RB2」を略して、「intTRB」としました。
割り込みプログラムのはじめに、「trbif_trbir = 0」を実行して、タイマRB割り込み制御レジスタ(TRBIR)の「タイマRB割り込み要求フラグ」を"0"にしています。割り込みが発生すると、「タイマRB割り込み要求フラグ」が自動的に"1"になります。このビットが"1"になると『タイマRB2割り込みが発生したんだ!!』とマイコンが認識して、vect=24で指定した関数を実行します。
もし、「trbif_trbir = 0」を実行しなければ、割り込みプログラムが終わってメインプログラムに戻った瞬間、また『タイマRB2割り込みが発生したんだ!!』とマイコンが認識してしまい、vect=24で指定した関数を実行します。実際には、まだ1msは経っていませんが、マイコンは「trbif_trbir」が"1"かどうかしか見ていません。よって、このような誤動作をしてしまうのです。この誤動作を防ぐために、割り込みプログラムのはじめに「trbif_trbir = 0」を実行しています。試しにこの行を消してプログラムを実行、どうなるか確かめてみてください。
次に、cnt_rb変数を1つ増やしています。よって、今回のプログラムではcnt_rb変数が1msごとに増えていくことになります。メインプログラム(割り込みプログラムではないプログラムのことです、main関数以外も含みます)でこの変数の値を数えると、正確な時間が分かります。
timer関数
timer関数は、時間を計る関数です。引数で設定した時間[ms]分、時間稼ぎをします。timer関数を下記に示します。
void timer( unsigned long timer_set )
{
cnt_rb = 0;
while( cnt_rb < timer_set );
}
はじめに、cnt_rb変数を0にします。この変数は先に説明した割り込みプログラム内で1msごとに増えていきます。while文で、cnt_rb変数が引数で設定した値のtimer_set変数より小さいなら、この行を繰り返し続けます。ようはこの行で指定した時間が経つまで待ち続けます。
main関数
main関数を下記に示します。
void main( void )
{
unsigned char d;
init(); /* 初期化 */
while( 1 ) {
p1 = 0b00001111; ←ポート1のLEDを点灯
timer( 200 ); ←0.2秒待つ
p1 = 0b11110000; ←ポート1のLEDを点灯
timer( 200 ); ←0.2秒待つ
p1 = 0b00000000; ←ポート1のLEDを点灯
timer( 200 ); ←0.2秒待つ
}
}
ポート1に接続されているLEDを光らせて、0.2秒待ちます。次に違うパターンでLEDを光らせて・・・ ということを無限ループで繰り返し続けます。「led.c」はfor文のループでtimer関数の時間を作っていましたので、正確な時間ではありませんでした。今回のプログラムは、内蔵クロックの20MHzを基準にして、タイマRB2で時間を計っていますので正確です。
for文を使ったループはなぜ正確ではないのか
「led.c」のtimer関数は、下記のようなプログラムになっています。
void timer( unsigned long timer_set )
{
int i;
do {
for( i=0; i<871; i++ );
} while( timer_set-- );
}
for文で、何もしないことを871回繰り返しています。この1行が1msです。この数値は実験で実測しました。したがって、実測しない限り、1msがどれくらいかは分かりません。
また、実測して1msが分かったとしても、「コンパイラがC言語をアセンブリ言語に変換したときに、どのようなアセンブリ言語になるか分からない」という問題があります。要は、コンパイラの変換結果によっては1msでは無くなると言うことです。
今回は「M16C Series and R8C Family C/C++ Compiler V.6.00.00」というバージョンのコンパイラを使っています。コンパイラがどのようなアセンブリ言語に変換したか、下記に示します。
_timer:
enter #02H
L5:
mov.w #0000H,-2[FB] ← i = 0を実行
L11:
cmp.w #871,-2[FB] ← 871か比較
jge L15 ← 以上ならL15へ
add.w #0001H,-2[FB] ← i++を実行
jmp L11
L15:
mov.w 5[FB],R0
mov.w 5+2[FB],R2
add.w #-1,5[FB]
sbb.w #0000H,5+2[FB]
cmp.w #0000H,R2
jne L5
cmp.w #0000H,R0
jne L5
exitd
このコードは、今回はたまたま、このようなアセンブリ言語に変換したが、次はどのように変換するかはC言語レベルでは分からないということになります。今回は「M16C Series and R8C Family C/C++ Compiler V.6.00.00」を使用しましたが、違うバージョンになるとコードが変わって、1msにならないかもしれません。また、同じV.6.00.00でも最適化条件を変えると、1msにならないかもしれません(といいますか、なりません)。
よって、for文によるtimer関数は、簡単に時間稼ぎができて良いのですが、条件によっては1msにはならないことがありますので、使用には注意が必要です。タイマRB2を使った方法は、基準が20MHzのクロックなのでコンパイラが変わろうが、アセンブリ言語のコードが変わろうが、影響ありません。
|