211.CMTで時間つぶし(SH2-7045F)


2002/05/31 【ソフトウエア編TOPに戻る】

今回は、SH2での時間潰しのお話です。H8でも同様の機能のご紹介をしていますが、今回も基本的に同じことをSH2で行ないます。マイコンの内部処理は非常に早いため、周辺機器とのインターフェイスでタイミングをとる場合や、人間に動作状態が分かるようにする場合には、わざわざある時間待機(時間潰し)させて、周囲の速度に歩調を合わせてあげます。

時間潰しの方法としては幾つか考えられますが、最も簡単なのはforによって無駄なループを繰り返すことでしょうか。これも手軽ではありますが、今回はCMT(コンペアマッチタイマ)機能を使ってみようと思います。CMTならば正確な時間潰しが可能ですし、時間潰し関数を作ってしまえば、今後も便利に利用できます。


211−1.CMT(コンペアマッチタイマ)機能

SH2-7045Fには、MTU(マルチファンクションタイマパルスユニット)などの高度なタイマ機能のほかに、単純で扱いやすく、簡単に時間計測ができるCMT(コンペアマッチタイマ)という機能が2チャンネル分内蔵されています

1チャンネル分のCMTには、16ビットタイマカウンタCMCNT(コンペアマッチタイマカウンタ)と、コンペアマッチ値を設定するためのレジスタCMCOR(コンペアマッチコンスタントレジスタ)がひとつずつあります。CMCNTをカウントアップさせるためのクロックは、CMCSR(コンペアマッチタイマコントロール/ステータスレジスタ)の設定によってφ(システムクロック周波数)/8、φ/32、φ/128、φ/512のいずれかから選択できます。なお、φシステムクロック周波数は、SH2ボード上のクロック倍率の設定によって変化します。

CMSTR(コンペアマッチタイマスタートレジスタ)のSTRビットを1にすることによってCMCNTはカウントを開始し、CMCORに設定されたコンペアマッチ値と一致したタイミングでCMCSRCMF(コンペアマッチフラグ)が0(ゼロ)から1に変化します。つまり、CMFの状態を監視することによって、コンペアマッチ状態になったかどうかが分かります。なお、コンペアマッチによって、CMCNTのカウント値はゼロクリアされます。

ごちゃごちゃと書きましたが、要は一定時間でカウントアップするカウンタが、予め設定した値になったらフラグが立つということです。カウンタがひとつカウントアップする時間周期は分かるので、設定した値までカウントアップするのに要する時間は簡単に求められます。つまり、カウントアップを開始してからフラグが立つまでの時間が、タイマーとして利用できるということになります。

なお、CMCSRCMIE(コンペアマッチ割り込みイネーブル)ビットを1にしておくと、コンペアマッチによって割り込みを発生させることができますが、今回は利用しません。

【先頭に戻る】


211−2.時間潰しプログラムのご紹介 cmttest1.c

では、時間潰し利用例のプログラムをご紹介します。ここではCMT0チャンネルを使ってwait( )という関数を作り、1mS単位で待機時間を指定できるようにしています。そして、PD16〜PD23PFC(ピンファンクションコントローラ)で出力ポートに設定して、1秒ごとにカウントアップする8ビットカウンタ動作を行ないます。

CMT機能自体は内部的なカウント動作であるため、PFCの設定などは特に気にしておらず、前回の210.SH2先ずはPIOから(SH2-7045F)の時と同じままにしてあります。また、割込み処理も行なっていないため、割り込みベクターテーブルファイルも、前回と一緒です。

プログラムソースはここをクリックするとダウンロードできますcmttest1.c  割り込みベクターテーブルはvector1.sですが、ダウンロードしたらvector.sという名前に変更してから利用して下さい。

 

/**********************************************************
cmttest1.c                                                                2002.05.26
                                                                              RIKIYA
SH2_7045F

CMT0を使って1mS単位の時間稼ぎ関数wait()を作る。
main()関数では、PD16〜PD23BITを1秒ごとにカウントアップ。
/*********************************************************/
#include "sh7040s.h"

void boot(void);
void __main(void);
int main(void);
void sectionInit(void);
void wait(unsigned short);

/* __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]-- */

     CMT0.CMCSR.BIT.CKS = 0;   /* CMT0 CLOCK/8      */
     CMT0.CMCOR = 3579 ;         /* 1mS/0.279uS = 3579 */
     CMT.CMSTR.BIT.STR0 = 0;    /* CMT0 STOP            */

     sectionInit(); /* メモリ初期化 */

     main(); /* メイン関数呼び出し */
}

/* メイン関数********************************************/
int main(void){

unsigned char i=0;

     for( ; ; ){
          i++;
          PD.DR.BYTE.HL = i;     /* PD16〜23 カウントアップ */
          wait(1000);                /* 1000mS待つ                  */
     }
     return 0;
}

/* CMT時間稼ぎ********************************************
引数の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;
}

/* セクション初期化 ************************************/
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;
     }
}

【先頭に戻る】


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

ちょっと見た感じは長めのプログラムですが、良く見てみると主要部分はちょっとだけです。

■インクルードと使用関数の型宣言

#include "sh7040s.h"

void boot(void);
void __main(void);
int main(void);
void sectionInit(void);
void wait(unsigned short);

お馴染みSH7040S.Hヘッダファイルの読み込み指定と、使用する関数の型宣言です。今回使用する関数で追加になっているのはwait( )関数だけで、あとは毎回出てくるものばかりです。この関数の型宣言をサボるとWarning(警告)が出ます。

 

■ダミー関数を置く

void __main(void){
}

すみません。ここは、こういうものだと思って置いてください。

 

■PFC(ピンファンクションコントローラ)の設定

boot( )関数の中身です。SH2が起動すると、一番はじめにこのboot( )関数が実行されるようにしています。 

/* 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]-- */

PFC設定については、今後少しずつ解説していきたいと思います。ここは前回の210.SH2先ずはPIOから(SH2-7045F)の時と同じままにしてありますので、そちらをご参照ください。

なお、今回のプログラム的には、

     PFC.PDCRH2.WORD = 0x0000; /* PD23〜PD16 */
     PFC.PDIORH.WORD = 0x00ff;   /* [OUT]PD23〜16 [IN]PD31〜24 */

の設定以外は無関係ですが、個人的な好みで全てのポート用の設定を書き込んでいます。

 

■CMT0チャンネルのレジスタ設定

     CMT0.CMCSR.BIT.CKS = 0;   /* CMT0 CLOCK/8      */
     CMT0.CMCOR = 3579 ;         /* 1mS/0.279uS = 3579 */
     CMT.CMSTR.BIT.STR0 = 0;    /* CMT0 STOP            */

今回のプログラムで言えば、ここが一番のポイントです。上の頭の2行でCMTの動作を設定しています。

     CMT0.CMCSR.BIT.CKS = 0;   /* CMT0 CLOCK/8      */

では、CMT0CMCSR(コンペアマッチタイマコントロール/ステータスレジスタ)の、CKS(クロック選択)ビットで、タイマカウンタのカウントアップ周期を決定しています。CKSは2ビット分あり、以下の動作となります。

ビット1 ビット0

カウント周期

CKS1 CKS0
0 0 φ/8
1 φ/32
1 0 φ/128
1 φ/512

ここで、φはシステムクロックですが、SH2は外部から供給される入力クロックに対して、内部処理的に2倍、4倍の倍率を設定することが可能です。従って、SH2のマイコンボード上にある倍率設定を切り替えることによって、φの示す周期も変わってきます。アルファプロジェクト製AP-SH2F-0Aの場合は、以下のようになります。

 

倍率 φ カウント周期 1カウントアップの時間
x1 7.15909MHz φ/8 1.11746μS
φ/32 4.46984μS
φ/128 17.8793μS
φ/512 71.5174μS
x2 14.31818MHz φ/8 0.55873μS
φ/32 2.23492μS
φ/128 8.93968μS
φ/512 35.7587μS
x4 28.63636MHz φ/8 0.27936μS
φ/32 1.11746μS
φ/128 4.46984μS
φ/512 17.8793μS

今回は、x4倍動作時にCMT1mSのコンペアマッチ周期を取るため、一番精度の出せるφ/8の設定にしています。

次に、

     CMT0.CMCOR = 3579 ;         /* 1mS/0.279uS = 3579 */

の部分では、カウントが開始されてからコンペアマッチが発生するまで、ちょうど1mSになるような値をCMT0のCMCOR(コンペアマッチコンスタントレジスタ)に書き込んでいます。

上記の表から、x4倍で動作中のφ/8は、0.27936μSでした。なので、3579(10進数)まで数えたところで、999.829μS(=1mS)ということになります。

最後の

     CMT.CMSTR.BIT.STR0 = 0;    /* CMT0 STOP            */

で、CMT0チャンネルのCMCNT(タイマカウンタレジスタ)を停止させておきます。

 

■メモリーの初期化と、メイン関数main( )の呼び出し

     sectionInit(); /* メモリ初期化 */
     main(); /* メイン関数呼び出し */

CPUの各レジスタの初期化が終わったら、今度はメモリーの初期化を行い、いよいよメイン関数を呼び出します。

 

■メイン関数main( )

int main(void){

unsigned char i=0;

     for( ; ; ){
          i++;
          PD.DR.BYTE.HL = i;     /* PD16〜23 カウントアップ */
          wait(1000);                /* 1000mS待つ                  */
     }
     return 0;
}

これが、今回のプログラム処理の主役部分です。

先ず、関数内で使用する変数 i を、unsigned char 型(8ビット符号なし整数 0 〜 255)で宣言しておきます。

for( ; ; ){  } 文で無限ループを作って、

          i++;
          PD.DR.BYTE.HL = i;     /* PD16〜23 カウントアップ */
          wait(1000);                /* 1000mS待つ                  */

を永遠に続けます。ここでは、先ず変数 をインクリメント(+1)して、その値をPD16〜PD23ビットに出力します。その後、wait( )関数を呼び出して1000mS(1秒)の時間潰しを行なっています。1秒が経過したら、再び i をインクリメントして、先ほどと同じことを繰り返します。

つまり、1秒ごとにPD16〜PD23の8ビット出力がカウントアップします。

 

■時間潰し関数 wait( )

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;
}

ようやく今回のページの目的である時間潰し関数wait( )の登場です。

wait( )関数では、引数に unsigned short 型(16ビット符号なし整数 0〜65535)の変数 msec を受け取ります。 で、この関数内で発生させる1mSの時間潰し処理を、for文でmsec 回数繰り返して、全体として必要なだけの時間潰しを行ないます。

先ず、

     CMT.CMSTR.BIT.STR0 = 1;  /* CMT0 START */

で、CMT0タイマーカウンタのカウントアップを開始させます。次に、早速msec回数のfor文に入ります。

 

          do{                             /* CMCNT = CMCORまで待つ(1s)*/
          }while(CMT0.CMCSR.BIT.CMF == 0);

では、CMT0のCMCSR(コンペアマッチタイマコントロール/ステータスレジスタ)のCMF(コンペアマッチフラグ)ビットをdo{  }while文によって常に監視し、CMFが1になるまで(コンペアマッチが発生するまで)じっと待ちます。コンペアマッチが発生すると、CMF になってdo{ }while文を抜け、CMT0.CMCSR.BIT.CMF = 0;によってCMFのフラグをゼロクリアしてあげます。

CMCNTタイマカウンタは、コンペアマッチが発生した時点で自動的にゼロクリアされて、勝手に再びカウントを開始します。なので、for文の比較分岐処理によるタイムラグは基本的に無関係です。

for文をmsec回数実行したら、

     CMT.CMSTR.BIT.STR0 = 0; /* CMT0 STOP */  

    return;

によってCMT0のCMCNTタイマカウンタを停止させ、wait( )関数を抜けます。wait( )関数の戻り値はvoid型に宣言しているために戻り値がないので、return; とします。 main( )関数と同じように return 0; などとすると、コンパイル時にwarning(警告)が出ます。

【先頭に戻る】


211−4.プログラムの動作

 

プログラムをダウンロードしてSH2マイコンボードの電源を入れると、PD16〜23ビットに接続した8ビットLEDが1秒ごとにカウントアップを開始します。

 

【先頭に戻る】


今回は、最も簡単なタイマーカウンタの動作をご紹介しました。今回作ったwait( )関数は、今後も色々と便利に使えると思いますが、当然時間潰しをしている間は、他のことは何もできません。まさに時間潰しです。そこで、次はCMTで割り込みを発生させ、一定周期ごとに決まった仕事をさせるインターバルタイマーの動作をご紹介することにします。お楽しみに。(^^

 

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


【表紙に戻る】