103.タイマーで正確な時間稼ぎをする


2001/03/06 【ソフトウエア編TOPに戻る】

103−1.タイマー機能を使う

 

H8-3048F CPUには、ITU(16bitインテグレーテッドタイマーユニット)という機能が5チャンネル分付いています。ITUではタイマーに関するさまざまな機能が扱えますが、今回はその中でも一番単純なタイマー機能を使ってみることにします。以下にタイマー機能の動作について簡単にご紹介します。

タイマー動作説明図

先ずタイマー動作をスタートさせると、16bitのTCNT(タイマカウンタ)がカウントを開始します。16bitなので、0000hからFFFFh(1〜65535)までカウントできます。

このカウンタは、ある基準となるクロック信号が1回 H(ハイ)/L(ロー)を繰り返す度に、1ずつカウントを増やしていきます。つまり、クロック信号の周期(時間)が分かっていれば、カウント値によってカウント開始から何秒経過したかが分かります。

ここで、GRA(ゼネラルレジスタA)に、0000hからFFFFhの間の好きな値を設定しておくと、カウント値がGRAと一致(コンペアマッチ)したときに、TSR(タイマーステータスレジスタ)IMFAというbitが1となり、一致したことを検出することができます。つまり、GRAに書き込む値によって、好きなタイマー期間を設定することができるのです。


103−2.正確な時間稼ぎをする関数

 

今までご紹介してきたプログラムの中では、for(i=0;i<0xffff;i++){ }といった無意味なループ処理をすることで時間稼ぎを行なう関数を使っていました。これでも成果はあったのですが、今回ご紹介するプログラムは、指定した時間で正確に時間稼ぎをする関数と、その使用例です。

ここでは、0から5チャンネルまであるITUの中から、ITU0を利用します。メイン関数の中で、先ずtimer_init();を実行してITU0の初期化を行い、wait( );という関数を呼び出します。wait( );関数のカッコの中には、時間稼ぎをしたい秒数をミリ秒単位で書き込んでおきます。1秒の時間稼ぎならwait(1000);とします。

下のメイン関数ではこの機能を使い、Port5のLED1と2を、正確に1秒ごとに点滅させています。

プログラムはここをクリックするとダウンロードできます。(timtest1.c)

/****************************************/
/* タイマーテスト     RIKIYA 2001/02/26 */
/*                           timtest1.c */
/*                             H8-3048F */
/* LED1とLED2を、正確に1秒で点滅させる  */
/****************************************/

#include <3048f.h>

/* 待ち時間発生初期化 ************************************/
void timer_init(void){
     ITU0.TCR.BYTE = 0x23;     /* GRAコンペアマッチ clock 1/8 */
     ITU0.GRA = 0x07d0;        /* GRAを2000に設定       */
     ITU.TSTR.BIT.STR0 = 0;    /* カウント停止状態          */
return;
}

/* 待ち時間発生 引数に、必要なミリ秒を指定する***********/
void wait(int msec){
int i;
     ITU.TSTR.BIT.STR0 = 1;      /* ITU0 TCNTカウント開始             */
     for(i=0;i<msec;i++){
          do{                    /* TCNT = GRAになるまで待つ(1mS) */
          }while(ITU0.TSR.BIT.IMFA == 0);
          ITU0.TSR.BIT.IMFA = 0; /* 検知フラグを戻して再開          */
     }
     ITU.TSTR.BIT.STR0 = 0;      /* ITU0 TCNTカウント終了             */
return;
}

/* メイン関数 ********************************************/
/* Port5のLED1とLED2を、1秒ごとに点滅させる */

void main(void){

     timer_init();              /* timerの初期化           */
     P5.DDR = 0xff;             /* port5出力に設定 表示LED */
     P5.PCR.BYTE = 0x00;        /* port5プルアップoff      */

     while(1){
          P5.DR.BYTE = 0x01;    /* LED1を点灯 */
          wait(1000);           /* 1000mS待つ */
          P5.DR.BYTE = 0x02;    /* LED2を点灯 */
          wait(1000);           /* 1000mS待つ */
     }

}

 【先頭に戻る】


103−3.プログラムの説明

 

■timer_init(); 待ち時間発生初期化の説明

 

wait関数を使うための初期化を、timer_init();というひとつの関数にまとめました。

【1】TCR(タイマーコントロールレジスタ)の設定

    ITU0.TCR.BYTE = 0x23;     /* GRAコンペアマッチ clock 1/8 */

TCRは、以下の8bitのレジスタです。

7 6 5 4 3 2 1 0
--- CCLR1 CCLR0 CKEG1 CKEG0 TPSC2 TPSC1 TPSC0

 

TPSC2,1,0では、TCNT(タイマカウンタ)の基準となるクロックを選びます。外部から入力するクロックも選べますが、システムクロックを分周(プリスケーラで遅く)して使用する場合だけを抜粋すると、以下の表となります。

TPSC2 TPSC1 TPSC0 説 明

0

0 0 内部クロック :φ(システムクロック)
0 0 1 内部クロック :φ/2
0 1 0 内部クロック :φ/4
0 1 1 内部クロック :φ/8

AKI-H8/3048Fのシステムクロックは、標準で16MHzです。今回はφ/8の設定で使用してみます。なのでここは 011 とします。

 

CKEG1,0では、クロックの立ち上がり、立下り、どちらのエッジのタイミングでカウントするかを選びます。

CKEG1 CKEG0 説 明
0 0 立ち上がりエッジでカウント
0 1 立ち下がりエッジでカウント
1 -- 両方のエッジでカウント

今回は立ち上がりエッジでカウントします。なのでここは 00 とします。

 

CCLR1,0では、TCNTのクリアの仕方を設定します。

CCLR1 CCLR0 説 明
0 0 TCNTクリアの禁止
0 1 GRAコンペアマッチとインプットキャプチャでクリア
1 0 GRBコンペアマッチとインプットキャプチャでクリア
1 1 同期動作をしているほかのタイマに合わせてクリア

今回、カウント値の設定はGRAに書き込みます。なのでここは 01 とします。

 

以上の設定をあわせると、以下のようになります。

7 6 5 4 3 2 1 0
--- CCLR1 CCLR0 CKEG1 CKEG0 TPSC2 TPSC1 TPSC0
0 0 1 0 0 0 1 1

これを16進数にすると、23hです。

なので、ITU0.TCR.BYTE = 0x23; となります。

 

【2】GRA(ゼネラルレジスタA)の設定

 ITU0.GRA = 0x07d0;         /* GRAを2000に設定 */

どこまでカウントしたらTCNTをクリアして、知らせてもらうかを設定します。今回のTCNTのクロックはφ(16MHz)/8に設定しました。なので、2MHzが基準クロックになります。2MHzの周期は0.5マイクロ秒で、2000倍するとちょうど1ミリ秒(1/1000秒)になります。なので、GRAを2000(16進数で07D0h)に設定すると、カウント開始から1/1000秒後にフラグが立って教えてもらえます。

 

【3】STR(タイマースタートレジスタ)で、タイマーを止めておく

 ITU.TSTR.BIT.STR0 = 0;       /* カウント停止状態 */

TCNTのカウントを止めておくか、スタートさせるかは、STRに書き込むデータで操作します。STRは以下のような8bitのレジスタで、ITU0〜5それぞれのTCNT0〜5を操作することができます。bit7,6,5は未使用で、読み出すと常に1となり、書き込みはできません。

7 6 5 4 3 2 1 0
--- --- --- STR4 STR3 STR2 STR1 STR0
1 1 1 0 0 0 0 0

STR0〜5の、該当するbitを0にするとカウントは停止し、1にするとカウントを開始します。今回は、STR0だけを0(停止状態)にしたいので、ITU.TSTR.BIT.STR0 = 0; でSTR0を0にしています。ITU.TSTR.BYTE = 0xE0; としても同様の結果となります。

 【先頭に戻る】

 

■wait(int msec){ } 待ち時間発生関数の説明

void wait(int msec){  } {  }で囲まれた部分が待ち時間発生のプログラムです。

 

【1】void wait(int msec)とは

void wait(int msec)の部分で、関数名waitの宣言と、引数msecの宣言をしています。wait関数自体は、呼び出し元の関数(main等)に対して値を返さない関数なので、何も返さないvoid型として宣言に加えています。引数のmsecは呼び出し元の関数からもらってくる値でint型の宣言をしています。関数の使い方(作り方)や引数の扱いについては、専門書をひも解いて下さい...。

 

【2】汎用の変数 i の宣言

int i;

まず、int型の i という変数を宣言しています。これはあとで使います。別に i である必要もありませんが、なにか回数を数えさせるような用途の変数は、i , j , k あたりを使ったりします。

 

【3】タイマースタート
 ITU.TSTR.BIT.STR0 = 1;     /* ITU0 TCNTカウント開始 */

STR0のbitを1にして、TCNTのカウントを開始します。つまり、タイマースタートです。

 

【4】汎用の変数 i を使ったループ

 for(i=0;i<msec;i++){  }

このループは、{  }の中の処理を引数でもらってきたmsecの回数だけ繰り返すという意味です。繰り返しの数を数えるために、さっき宣言した汎用の変数 i を使用しています。すぐ後に説明しますが、{  }の中では、正確に1/1000秒の時間つぶしをする処理を行います。なので、このforループでは 1/1000秒 x msec倍の時間つぶしを行なうことになります。

 

【5】1/1000秒のカウント

          do{               /* TCNT = GRAになるまで待つ(1mS) */
          }while(ITU0.TSR.BIT.IMFA == 0);

do{  }while( ) ループは、( )の中の条件が成立する間、{  }の中の処理を繰り返します。ここでは、ITU0.TSR.BIT.IMFA == 0が成立する間ということになります。ところでIMFAって何?という感じなので、以下にご説明します。

IMFAは、TSR(タイマーステータスレジスタ)の中のbitのひとつで、TCNTがGRAと同じ値になった(コンペアマッチ)時にになります。なので、上記の条件文は、「ITU0のTSRのIMFAビットが0である間(タイマーが値に達しない間)は、ループを繰り返しなさい」ということになります。TCNTがGRAと同じ値になるのは、カウントを開始してからちょうど1/1000秒後になるので、その時にループから抜けます。

ちなみに、TSRはITUの各チャンネルごとにあり、各々には以下のbitがあります。

7 6 5 4 3 2 1 0
--- --- --- --- --- OVF IMFB IMFA
1 1 1 1 1 0 0 0

OVF

TCNTがカウントアップ動作の時にFFFFhを超える(オーバーフロー)か、カウントダウン動作の時に0000h以下になる(アンダーフロー)と1になります。

IMFB

TCNTがGRB(ゼネラルレジスタB)と同じ値になると1になります。

IMFA

TCNTがGRA(ゼネラルレジスタA)と同じ値になると1になります。

 

【6】TCNTのカウント停止と、wait関数呼び出し元への戻り

     ITU.TSTR.BIT.STR0 = 0;     /* ITU0 TCNTカウント終了 */
return;

先ほど1にしたSTR0のbitを、0にしてあげるとカウントを停止します。そして、return;でこのwait関数を呼び出した元の関数に処理を戻します。

 【先頭に戻る】

 

■main関数の説明

main関数の中身については、もうほとんど説明の必要はないかとも思います。

【1】先ず始めにITU0の初期化を行います。

     timer_init();              /* timerの初期化 */

【2】LEDを点灯させるためにport5を出力にセットします。

     P5.DDR = 0xff;             /* port5出力に設定 表示LED */
     P5.PCR.BYTE = 0x00;        /* port5プルアップoff      */

【3】port5から01h(0000 0001)と02h(0000 0010)を、交互に出力します。そのとき、間にwait(1000);を挟んでいます。これで1秒間隔で点滅を繰り返します。

     while(1){
          P5.DR.BYTE = 0x01;     /* LED1を点灯 */
          wait(1000);            /* 1000mS待つ */
          P5.DR.BYTE = 0x02;     /* LED2を点灯 */
          wait(1000);            /* 1000mS待つ */
     }

wait関数を実行するには、このようにカッコの中に待ち時間をミリ秒(1/1000秒)の単位で入力します。wait(1000);なら1秒になります。

 【先頭に戻る】


だらだらと長い紹介になってしまいました。すみません。タイマーにはもっと色々な機能があります。ここでの使用例は、ほんの入口といったところです。

 

 

【ソフトウエア編TOPに戻る】


【表紙に戻る】