プログラムを読んでみよう② ―― 間隔計測masure.c
関数はひとつ、MeasureIntervals()だけです。
プログラムの説明の前に、どんなふうに間隔を計測するのかについて説明します。図2をご覧ください。これはスプーンをたたいた時、音検出回路の接続された入力ポートにどんな信号が入力されるかを示しています。
無音時は入力ポートは1になっています。スプーンをたたくと、入力ポートに0のパルスが入力されます。この幅は数十ミリ秒~最大50ミリ秒くらいです。
これに対し、ソフトウェアではまず図3のように①無音の間待って、②最初の音信号が来たところでストップ・ウォッチ(タイマー)をスタートさせます。
次に図4の③~⑥のように、無音の確認と音信号の確認を繰り返し、2発目と3発目をやりすごします。
最後に図5の⑦~⑧で4発目の音を確認したところでストップウォッチを止めます。計測した時間は4回スプーンをたたいた時間であり、これを3で割ることでスプーンを打った平均間隔時間が求められます。
それでは処理プログラムの説明に移ります。
まず22行目でWaitInactive()を呼んで無音状態を確認します。引数にNOCHEK_TIMEを渡しているのは、タイムアウト(時間切れ)処理をせず、いつまでも無音になるのを待てという指示です。
無音を確認したら、24行目でWaitSound()を呼んで、今度は最初の1音を待ちます。これも引数にNOCHECK_TIMEを指定していて、スプーンが叩かれるまで永遠に待ちます。音が検出されるとWaitSound()から帰ってきますので、ここでStartTimer()を呼んで時間計測を開始します。本来ならWintSound()の処理の中で、ポートの変化(音の検出)と同時にタイマをスタートさせるほうがより正確ですが、ここでは制御の順番のわかりやすさを優先しています。27行目では1番目のLEDを点灯します。1番目のLEDというのが実際はどのランプなのかはここではわかりません。先に述べたように、それはデバイスドライバの中で解決しています。ここでは「1番目のLED」を灯すという抽象的動作にしてあるのです。
図5からわかるように、2番目から4番目の音までは同じ処理、つまり「無音を確認して次の音を待つ」という動作を繰り返します。繰り返しということはループ制御文の出番ということで、ここではfor文を使っています。繰り返し回数は変数iで制御していて、2(番目)から4(番目)まで繰り返すよう記述してあります。
30行目からが繰り返す処理の内容です。最初に1ミリ秒単位の時間待ち関数Wait1msec()を呼んで50ミリ秒待っています。これは、スプーンを1回叩くと、最長で40~50ミリ秒の信号が発生するためです。音信号が消えたころを見計らって、31行目でWaitInactive()を呼んで無音状態に戻るのを待ちます。ここでは引数にCHECK_TIMEを指定して、一定時間以上無音が確認できないときにはタイムアウトしてエラーで帰ります。
無音状態が正常に確認できたら(33行目)、34行目でスプーン音を待ちます。ここも一定時間音が来なければタイムアウトしてエラーが返ります。音が確認できたら、36行目で対応するLEDを点灯します。for文の回数制御変数iの値を、音が「何番目か」に合わせてあるので、点灯させるLED番号にはiを渡すことで対応LEDを指定できます。
40行目は無音確認~音確認までの一連の処理でエラー(タイムアウト)があったかをチェックをしています。エラーがあった場合は、41行目でタイマを止めて、ERRORで帰ります。
こうして正常に4回目の音まで検出しおわると、for文を終了し、46行目でReadTimer()を呼んでタイマ値を読みます。47行目で1/3することで、スプーンを打つ間隔の平均値を求めています。この値があまりに小さいとき(今回のプログラムでは100ミリ秒未満)は、計測に失敗した異常値とみなしてエラーにしています(51行目)。
前後しますが、49行目でタイマを止め、正常時はスプーンをたたいた間隔の平均時間を持って帰ります。