2004/03/07 【ソフトウエア編TOPに戻る】
以前にITU(インテグレーテッドタイマーユニット)を利用して時間稼ぎをさせたり、PWM波形を作ったりする方法をご紹介しましたが、今回は同じくITUを利用して、入力されたパルス信号の時間幅(パルス幅)を計測する方法をご紹介しましょう。パルス幅の計測は、加速度センサーや距離センサーなど、様々なセンサーなどからの出力をマイコンに取り込む上で良く使われます。TekuRoboにも問い合わせが多い分野ですので、大変遅くなりましたがここでご紹介することにします。(^^;
ITUを使ったパルス幅の計測は、ちょうど時間稼ぎの時の使い方と逆の使い方をします。時間稼ぎの時にはカウンタTCNTの値が予め設定しておいたGRAやGRBの値と一致したときに、TSR(タイマーステータスレジスタ)のIMFAフラグやIMFBフラグが立ったことを検出することによって一定時間を作っていました。忘れてしまった方は103.タイマーで正確な時間稼ぎをするのページなどをご参照下さい。一方、パルス幅の計測をする場合には、パルス信号の始まりを検出してカウンタTCNTのカウントを開始し、パルス信号の終わりが検出された時のTCNTの値を読み取ることによって行います。このとき、パルス信号の始まりと終わりをどうやって検出するか、そしてTCNTの値をどうやって読み取るかによってプログラミングの方法が変わってきます。
![]() |
H8-3048FのITUの場合、TCNTは16BITカウンタで65535まで数えられます。また、クロック周波数を16MHzで動かしているときにクロックの1/8の速度でカウントを行うように設定した場合、1カウントに0.5μS(μマイクロは100万分の一)掛かります。なので、TCNTで65535まで数えるのに要する時間は、 65535 x 0.5μS = 32.7675mS であり、これがオーバーフローなしで計測できるパルス幅の上限ということになります。 パルス信号の始まりと終わりを検出するには、プログラム上で常に入力を監視してその変化を調べるポーリング方式と、ITUのインプットキャプチャ機能を利用してフラグの変化を監視する方式、そして同じくインプットキャプチャによる割り込み機能を利用した方式などが考えられます。 ITUのインプットキャプチャ機能を利用すると、パルスの立ち上がりと立下りを自動で検知し、そのときのTCNTの値を自動でGRAやGRBに書き込んでくれる機能などがあり便利です。
|
では、これから以下のプログラムをご紹介します。
116−1.PIOの入力機能によるパルス幅の計測 inputtest1.c
116−2.インプットキャプチャ機能のフラグ監視によるパルス幅の計測 inputtest2.c
116−3.インプットキャプチャ機能の割り込みによるパルス幅の計測 inputtest3.c
116−4.計測結果をμS単位に変換して一定間隔でシリアルポートから出力する inputtest4.c
なお、今回ご紹介するプログラムはGDL環境で制作しています。また、テスト用のパルスはPICを使って1mSと2mSのパルスを1秒ごとに繰り返し出力する環境を作りました。
116−1.PIOの入力機能によるパルス幅の計測 inputtest1.c
まずはじめに、もっとも分かりやすい方法でパルス幅の計測を行って見ましょう。このプログラムではカウンタとしてITU1チャンネルのTCNTを利用していますが、あとはITUの特別な機能は利用せず、PIOの操作だけでパルス幅の計測を行っています。
ここをクリックするとダウンロートできます。inputtest1.c
| /**************************************************/ /* パルス幅の計測 単純基本系 RIKIYA 2004/02/20 */ /* inputtest1.c */ /* H8-3048/F */ /* PIO機能だけのエッジ検出によるパルス幅計測 */ /**************************************************/ #include <3048.h> /* メイン関数************************************************/ int main(void){ long counter; P1.DDR = 0xff; // port1出力に設定 表示LED PA.DDR = 0x00; // portA入力に設定 パルス入力用 ITU1.TCR.BYTE = 0x03; // カウンタクリア禁止 clock 1/8 ITU.TSTR.BIT.STR1 = 0; // カウント停止状態 while(1){ while(PA.DR.BIT.B4 == 0){} // パルス立ち上がり検出 ITU.TSTR.BIT.STR1 = 1; // カウント開始 while(PA.DR.BIT.B4 == 1){} // パルス立下り検出 ITU.TSTR.BIT.STR1 = 0; // カウント停止 counter = ITU1.TCNT; // カウント値取得 P1.DR.BYTE = counter >> 4; // 4ビット下位にシフト ITU1.TCNT = 0; } } |
■プログラムの説明
パルス信号はPAポートのビット4から入力します。PAポートは入力に設定しておき、ビット4を常に監視し続けて、入力が0から1に変化したことでパルスの始まりとみなしてTCNTのカウントを開始させます。その後、1から0に変化したことでパルスの終わりとみなしてTCNTのカウントを停止させ、そのときのTCNTの値を読み取ります。読み取ったTCNTの値はlong形の変数counter に格納し、計測結果をP1ポートに出力するため確認しやすいように4ビット分下位方向にシフトさせています。
プログラム上のポイントは以下の通りです。
ITU1.TCR.BYTE = 0x03; // カウンタクリア禁止 clock 1/8
ここではカウンタTCNTのクリアが自動で行われることを禁止し、カウントアップのためのクロックをシステムクロック(16MHz)の1/8に設定しています。
while(PA.DR.BIT.B4 == 0){} // パルス立ち上がり検出
ITU.TSTR.BIT.STR1 = 1; // カウント開始
ここのwhile文では入力パルスの立ち上がり(パルス信号の始まり)を検出しています。実際は「検出」ということはしておらず、「入力信号が無い間はずっと待っていろ」といっているだけです。パルス信号が入力されて1になるとようやく次に進み、TCNTのカウントアップ(計測)を開始します。
while(PA.DR.BIT.B4 == 1){} // パルス立下り検出
ITU.TSTR.BIT.STR1 = 0; // カウント停止
counter = ITU1.TCNT; // カウント値取得
ここのwhile文では入力パルスの立下り(パルス信号の終わり)を検出しています。ここも「入力信号が無くなるまでずっと待ってろ」といっているだけですが... パルス信号が無くなって0になると次に進み、TCNTのカウントアップを停止させ、そのときのカウント値を変数counterに格納します。これでパルス幅の計測は終わりです。
P1.DR.BYTE = counter >> 4; // 4ビット下位にシフト
さて、計測した結果を見たいので、P1ポートに接続してあるLEDに表示させることとします。このとき、テスト環境である1mSと2mSの入力パルスを計測すると、変数counterには7D0hとFA0hのデータが格納されることになりますが、P1ポートは8BITなので表示し切れません。そこで、計測したデータを下位方向に4BITシフトさせて、上位8BITだけを表示させるようにしています。
テスト環境では以下のように表示されれば成功ということになります。
入力パルス幅が1mSの場合 → ○●●● ●●○● (7Dh)
入力パルス幅が2mSの場合 → ●●●● ●○●○ (FAh)
ITU1.TCNT = 0;
最後に、次の入力パルスの計測に備えてTCNTをゼロにクリアしておきます。
116−2.インプットキャプチャ機能のフラグ監視によるパルス幅の計測 inputtest2.c
次に、ITUのインプットキャプチャ機能を使ってみましょう。今回はパルスの立ち上がりをPIO操作で検知して計測を開始し、パルスの立下りをインプットキャプチャ機能のフラグを監視することで検出してみます。
ここをクリックするとダウンロードできます。inputtest2.c
| /**************************************************/ /* パルス幅の計測 単純基本系2 RIKIYA 2004/02/20 */ /* inputtest2.c */ /* H8-3048/F */ /* キャプチャフラグ検出によるパルス幅計測 */ /**************************************************/ #include <3048.h> /* メイン関数************************************************/ int main(void){ long counter; P1.DDR = 0xff; // port1出力に設定 表示LED PA.DDR = 0x00; // portA入力に設定 パルス入力用 ITU1.TCR.BYTE = 0x23; // GRAインプットキャプチャ,カウンタクリアGRA clock 1/8 ITU1.TIOR.BYTE = 0xCD; // GRA立下り,GRB立ち上がりインプットキャプチャ ITU.TSTR.BIT.STR1 = 0; // カウント停止状態 while(1){ while(PA.DR.BIT.B4 == 0){} // 入力パルス立ち上がり検知 ITU.TSTR.BIT.STR1 = 1; // ITU1 TCNTカウント開始 while(ITU1.TSR.BIT.IMFA == 0){} // GRAキャプチャ,パルス立下り検知 ITU1.TSR.BIT.IMFA = 0; // フラグをクリア ITU.TSTR.BIT.STR1 = 0; // ITU1 TCNTカウント停止 counter = ITU1.GRA; // カウント値の取得 P1.DR.BYTE = counter >> 4; // 8bitシフトさせP1に表示 } } |
■プログラムの説明
入力パルスの立ち上がりは、先ほどと同じようにPIOで入力信号の変化を見ることで検知しています。そして、パルスの立下り(終わり)の検知にGRAへのインプットキャプチャ機能を利用しています。GRAへのインプットキャプチャ機能を有効にするためにTCRへの設定を行い、パルスの立下りでインプットキャプチャを行うためにTIORへの設定を行っています。入力パルスの立下りタイミングでGRAへのインプットキャプチャが発生すると、TSRのIMFAビットが1に変化するため、プログラム上で立下りを検知することができます。インプットキャプチャが発生すると、GRAにTCNTの値が転送されるため、GRAの値を取得することによって、パルス幅の計測ができることになります。
ところで、ITU1チャンネルのGRAでインプットキャプチャをしようとする場合、その入力端子はハード的に決められており、PAポートのビット4と共用することになっています。(TIOCA1端子)
プログラム上のポイントは以下の通りです。
ITU1.TCR.BYTE = 0x23; // GRAインプットキャプチャ,カウンタクリアGRA clock 1/8
TCR(タイマーコントロールレジスタ)では、TCNTのカウンタクリアを行う要因やカウントアップのタイミング、そしてカウントアップの速度など、基本的な事項の設定を行っています。ここでは、以下の動作に設定しています。
1.GRAのコンペアマッチ/インプットキャプチャでTCNTをクリア
2.外部クロック立ち上がりエッジでカウント(今回は関係なし)
3.内部クロック:φ/8でカウント
ITU1.TIOR.BYTE = 0xCD; // GRA立下り,GRB立ち上がりインプットキャプチャ
TIOR(タイマーI/Oコントロールレジスタ)では、GRAとGRBをコンペアマッチで使うか、インプットキャプチャで使うかの選択や、インプットキャプチャの場合にはキャプチャを行うタイミングを、入力パルスの立ち上がり/立下り/もしくは立ち上がりと立下りの両方から選択することができます。ここでは以下の動作に設定しています。
1.GRBは立上がりエッジでインプットキャプチャ(今回は未使用)
2.GRAは立下りエッジでインプットキャプチャ
while(PA.DR.BIT.B4 == 0){} // 入力パルス立ち上がり検知
ITU.TSTR.BIT.STR1 = 1; // ITU1 TCNTカウント開始
入力パルスの立ち上がり検知とTCNTのカウント開始は、前項のプログラムと同じです。
while(ITU1.TSR.BIT.IMFA == 0){}
// GRAキャプチャ,パルス立下り検知
ITU1.TSR.BIT.IMFA = 0; // フラグをクリア
ここでは、GRAへのインプットキャプチャが実行されたことを示すTSR(タイマーステータスレジスタ)のIMFAビットが1に変化するまで待機し、1に変化したらIMFAフラグをクリアして0に戻しています。この時点で、すでにGRAにはインプットキャプチャが発生した時のTCNTの値が格納されています。
counter = ITU1.GRA; // カウント値の取得
そして、変数counterにITU1チャンネルのGRAの値を転送します。
プログラムの実行結果は前項のプログラムとまったく同じです。入力パルスの立下りタイミングの検出にITUの機能を利用しているだけで、基本的には前項のプログラムと処理のプロセスに変わりありません。
116−3.インプットキャプチャ機能の割り込みによるパルス幅の計測 inputtest3.c
さて、今までのプログラムではパルス信号が入力されるまでひたすら待ち続け、パルス信号が終わるまでやはりずっと待ち続けるというものでした。つまりパルス信号のエッジを検出するのに精一杯で、他に仕事ができないため非常に効率が悪いのです。そこで登場するのが割り込み処理です。インプットキャプチャ機能では、インプットキャプチャが実行されたタイミングで割り込みを発生させることができますので、それを利用します。今回は入力パルスの始まりと終わりの両方のタイミングを割り込みで検知することにしましょう。
ここをクリックするとダンロードできます。inputtest3.c
| /**************************************************/ /* パルス幅の計測 割り込み RIKIYA 2004/02/26 */ /* inputtest3.c */ /* H8-3048/F */ /* キャプチャ割り込みによるパルス幅計測 */ /**************************************************/ #include <3048.h> /* メイン関数************************************************/ int main(void){ EI; // 割り込みマクロ P1.DDR = 0xff; // port1出力に設定 表示LED PA.DDR = 0x00; // portA入力に設定 パルス入力用 ITU1.TCR.BYTE = 0x23;
// GRAインプットキャプチャ,カウンタクリアGRA clock 1/8 while(1){ } //
何もしない |
■プログラムの説明
main関数では、PIOとITU1の初期設定を行っているだけです。main関数内のwhile文にはmain関数で実行させたい主な処理を入れますが、今回は何も行わせていません。
int_imia1関数が、割り込み発生時に処理される部分です。今回はパルスの立ち上がりと立下りの両方でGRAのインプットキャプチャによる割り込みが発生するので、関数内でそれぞれの処理を分けています。プログラムを見ていただくと分かる通り、入力パルスの立ち上がりでは何もせず、立下りでGRAの値の取得と、P1ポートへの表示を行っています。プログラミング上では立ち上がりタイミングでは何もしていませんが、TCR(タイマーコントロールレジスタ)の設定によって、TCNTのクリアが自動的に行われています。なので、パルス入力の始まりを検出してTCNTをクリアし、新たに数えなおすといった動作を自動で行っていることになります。
プログラムの要点は以下の通りです。
■main関数
EI; // 割り込みマクロ
先ずは割り込みを実行するためのおまじないをしておきます。
ITU1.TCR.BYTE = 0x23; // GRAインプットキャプチャ,カウンタクリアGRA clock 1/8
TCR(タイマーコントロールレジスタ)は、前項のプログラムと同じく以下のような設定にしています。
1.GRAのコンペアマッチ/インプットキャプチャでTCNTをクリア
2.外部クロック立ち上がりエッジでカウント(今回は関係なし)
3.内部クロック:φ/8でカウント
ITU1.TIOR.BYTE = 0xCF; // GRA両エッジインプットキャプチャ
ここでは、TIORを以下の設定にしています。
1.GRBは立上がりエッジでインプットキャプチャ(今回は未使用)
2.GRAは立ち上がりと立下りの両エッジでインプットキャプチャ
ITU1.TIER.BIT.IMIEA = 1;
// IMFAフラグ割り込み許可
ITU.TSTR.BIT.STR1 = 1; // カウント開始
ここではTIER(タイマーインタラプトイネーブルレジスタ)のIMIEAビットを1にして、GRAのインプットキャプチャによる割り込み発生を許可しています。これを忘れると割り込みしてくれません。そして、TCNTのカウントを開始させます。
while(1){ } // 何もしない
さて、この部分が主な処理部分です。つまり、何もしないのがmain関数のお仕事ということになります。なので、割り込み処理が発生しない限り、パルス幅の計測を実行させることはできません。
■int_imia1関数
ITU1.TIER.BIT.IMIEA = 0;
// IMFA割り込み禁止
ITU1.TSR.BIT.IMFA = 0; // フラグをクリア
割り込み関数の中では、先ずTIERのIMIEAをクリアして割り込み禁止にしておきます。続いてTSRのIMFAをクリアして、割り込み処理が発生したよというフラグを元に戻してあげます。これは次の割り込みを受け付けるための準備作業です。
if(PA.DR.BIT.B4 == 1){
// 入力パルス立ち上がりの場合
}
入力パルスの立ち上がりタイミングで割り込みが発生した場合、このif文の中を実行します。PAポートのビット4はパルスが入力される端子ですが、割り込みが発生した直後に1になっているならば、パルスの立ち上がりだとみなしています。
そして、このときにはプログラム的には何の処理もしていません。しかし、TCRの設定でGRAのインプットキャプチャが発生したらTCNTのカウント値をクリアするように指定しているので、このタイミングでTCNTが0になり、新たに0から数えなおす動作をしていることとなります。
if(PA.DR.BIT.B4 == 0){
// 入力パルス立下りの場合
counter = ITU1.GRA;
P1.DR.BYTE = counter >>
4; // 4BITシフトさせP1に表示
}
入力パルスの立下りタイミングで割り込みが発生した場合、このif文の中を実行します。割り込みが発生した直後に入力が0になっているならば、パルスの立下りであるとみなしています。で、GRAにはインプットキャプチャが発生した時のTCNTの値が格納されているので、counter = ITU1.GRA;で変数counterにキャプチャしたデータを転送し、同様にP1ポートに表示させるための処理をしています。
ITU1.TIER.BIT.IMIEA = 1; // IMFA割り込み許可
割り込み処理の最後には、もう一度割り込み許可の設定を行って、次の割り込み発生に備えます。
116−4.計測結果をμS単位に変換して一定間隔でシリアルポートから出力する inputtest4.c
さて、ここでもう少しだけ、計測したデータを実際に利用した事例をご紹介することにしましょう。ここでは、計測結果を一定間隔でシリアルポートから出力するプログラムをご紹介します。計測結果はGRAのカウント数のままでは分かりづらいので、μS単位の数値として出力することにします。そうすればハイパーターミナルなどに接続すると何秒のパルス幅なのかがひと目でわかりますね。
以下のプログラムは、inputtest3.cをちょっと修正しただけです。主な修正箇所を青字にしてみましたのでご覧ください。
ここをクリックするとダウンロードできます。inputtest4.c
|
/**************************************************/ /* 割り込み処理 *********************************************/ |
■プログラムの説明
いかにinputtest3.cからの変更点をご説明します。
#include <myfunc.h>
これはシリアル通信やタイマーによる時間つぶしを行うための関数が収められた自分用のヘッダファイルを読み込んでいます。
151.自分の関数をヘッダファイルにするのページをご参照下さい
long counter; // グローバル変数宣言
inputtest3.cでは、変数counterは割り込み関数内のローカル変数として宣言していましたが、ここではすべての関数で共通して利用できるグローバル変数として宣言しています。この変数は割り込み関数内で値が更新され、main関数内でその値が参照されてシリアル通信で出力する数値のデータとして利用しています。
timer_init();
// タイマー初期化
sci1_init();
// sci1初期化
これは#include <myfunc.h>で読み込んだヘッダファイルの中で記述されている関数です。ITU0チャンネルを利用した時間つぶしと、sci1チャンネルを利用したシリアル通信を行うための初期化処理をしています。
while(1){
wait(200);
// 0.2秒の待機
sci_number_send(counter);
// 計測結果をuS単位でシリアル出力
}
これがmain関数内で主に行わせたい処理を記述している部分です。inputtest3.cでは、なにもしなかったところです。今回は、0.2秒待ってから計測結果のデータ(counter)をシリアル通信で出力する処理を行っています。つまり、0.2秒おきに出力を繰り返します。この部分だけを見ると、counterの値は変化しないのでは?と思われるかもしれませんが、パルスが入力される始まりと終わりのタイミングで毎回割り込みが発生するため、「main関数が知らないうちに」割り込み関数内で値が更新されているのです。これが割り込み処理の強みです。
/* 計測結果のシリアル通信出力 *******************************/
int sci_number_send(long counter){
char tx_data[10];
long data;
data = counter/2;
// カウント速度0.5uSなので 1/2にする
tx_data[0]=data/10000 + 0x30; data%=10000;
tx_data[1]=data/1000 + 0x30; data%=1000;
tx_data[2]=data/100 + 0x30; data%=100;
tx_data[3]=data/10 + 0x30;
data%=10;
tx_data[4]=data + 0x30;
tx_data[5]='u';
tx_data[6]='S';
tx_data[7]='\r';
tx_data[8]='\n';
tx_data[9]='\0';
sci1_strtx(tx_data);
// 文字列送信
}
これは独立した関数で、counterというlong型の計測結果を受け取り、シリアルポートから最大5桁までの秒数を示す文字列を出力します。秒数はμ(マイクロ1/1000)秒単位に換算されます。例えば、7d0h (2000)という計測結果を得た場合には "1000uS"という文字列と、改行コードを出力します。
void int_imia1(void){
割り込み関数内は、inputtest3.cと比べて大きな違いはありません。もともと割り込み関数内で宣言していた long counter;をなくしたくらいです。
■プログラムの実行結果は...
H8とパソコンをシリアルケーブルで接続し、ハイパーターミナルで通信を行います。ハイパーターミナルは以下の通信設定にしてください。
今回の実験環境は、6.AKI−H8小型マザーボードをターゲットとして、9.PIC学習用ボードからパルス信号を供給しています。

PAポートのビット4にパルス信号を与える環境を整えたら、H8マイコンボードの電源を入れます。すると、以下のように0.2秒ごとに計測結果が送られてきます。
![]() |
01004uSと02003uSという文字が4〜5個ずつ交互に表示されている様子が分かりますか? 01004uSは1004μS、つまり1.004mSを示し、02003uSは2003μS、つまり2.003mSを示しています。今回テスト用に供給しているパルス信号は、1mSと2mSを1秒おきに与えているため、ほぼ正確に計測できていることが分かります。 では最後の端数(4や3)は何かというと、パルスのエッジが検出されてから実際にインプットキャプチャがハード的に実施されるまでの約1.5クロック分の誤差は予定されていますが、それ以上の誤差分としては、PICで作っている信号源が持つ誤差だったり、PIC側とH8側のクロック周波数の差だったり、いろいろと考えられます。μSに換算する際に行っている1/2という演算でも大きな誤差が生じてきます。ここではあまり追求しないことにしましょう。 少なくとも、10μSオーダーまでは誤差なく計測できているようなので、実用上はあまり問題にならないレベルではないかと思います... ちなみに、参考程度までにPIC側のパルス発生のプログラムは、ここをクリックするとダウンロードできます。PulseGen1.c 内容的には特に解説の必要はないでしょう。(^^;
|
もっと早くにご紹介すべき機能だったかと思いますが、遅くなってすみませんでした。実際にセンサーを使い出してからまじめにやろうと思っていたものですから、後回しになってしまっていました。センサー出力を受ける目的でのパルス幅の監視は、どちらかというと常に監視して、その状態の変化によって他のコンポーネントを制御するためのネタに使うことが多いのではないかと思います。つまり、割り込み処理で使うことが一番実用的かなと思います。あまり割り込み処理を増やしすぎてもマイコンのパフォーマンスに影響を与えますし、どのようにシステムを組むのがよいのか、考えどころです。ちょっと思うに、こういったセンサーからの情報を常に受けとる専用のマイコンを準備しておき、必要な時にだけメインのマイコンに情報を与えてあげるようなシステムにしたほうが有効か、とも思ったりしますが如何でしょうね。(^^;