GVCの開発をしはじめた時、というかしているときにOSCで福岡に行ったら、
『ブラインドがふわっとゆれたらそれを検知する方法はないですか?』との
質問を受けました。
自分的には動態検知するものでいいのではないかと思ったのですが、ふわっ
とした時には窓を開けられて侵入されているとのこと。(さすが修羅の国!?)
なので離れた場所から…となると、温かいものが動いたら判る焦電センサー
とかレーザーなどによる測距センサーはどうかな?ということで、いろいろ
調べて、超音波測距センサーのLV-EZ4を当時ポチッた訳ですが、これが妙に
高い。
で、しばらくしてからオープンハードカンファレンスで『HC-SR04というのが
大陸からすごく安く手に入るよ』と教えてもらって調べてみたら、本当に嘘
みたいな値段で入手可能だったので、これを大量にポチッて対応することに
しました。
LV-EZ4は超音波の送信部と受信部が一体型になっているのでコンパクトで、
しかも距離情報の出力方式がいくつもあるのですが、HC-SR04は距離情報が
パルス幅で出てくるだけで、それを元に計算しなさいということでした。
Arduinoには指定ポートのパルス幅を返してくれる便利な関数がありますが、
GVCで使用しているPICというかXC8にはそんなものはありません。(ある?)
なので、PICの状態変化割り込み(IOCx)を使います。これはあらかじめ指定
したポートの状態がHighからLow、またはその逆になったときに割り込みが
発生する、というものです。
幸い(…というか実際にはあれこれやっていたら気がついたのですが)HC-SR04
が測定できる距離(450cm)というのは、温度20度の理想状態での音速として
13105マイクロ秒くらいで往復する距離になります。
実はGVCでは、PICのタイマーを16MHzでプリスケーラは1:8としているので、
1カウントあたり2マイクロ秒になります。なので16ビットカウンタの最大値
である65536カウントの場合、65536×2us=131072usということで、ちょうど
いい秒数になります。(HC-SR04の中も16ビットタイマで似たような感じで
やっているんでしょうかねー?)
というわけで、現在のGVCではパルスから距離の計算がとてもしやすいです。
測定の手順としては
・あらかじめ状態変化割り込みの設定をする
(ポートを入力モードに、割り込み発生ポートの指定)
・状態変化割り込みを許可
・HC_SR04のTrigポートをHigh(=1)にする
・10us以上待つ
・タイムアウトタイマースタート
(Lowのままの場合があるらしいので念のため)
・距離測定タイマースタート
・HC_SR04のTrigポートをLow(=0)にする
// 状態変化割り込み許可(=1)に INTCONbits.RBIE = 1; // HC_SR04のTrigポートをHigh(=1)にする PORT_HC_SR04_TX = 1; // 10us以上待てというので30us待つことにする Delay_10us(3); // ------------------------------ // タイムアウトタイマースタート // ------------------------------ // タイマー0カウンタを初期化 timer0_count = 0; // タイマー値設定 TMR0H = timer0_h; TMR0L = timer0_l; // タイマー割り込みフラグ初期化 INTCONbits.TMR0IF = 0; // タイマー0割り込み許可(=1) (Timer0 Overflow Interrupt Enable bit ... INTCON) INTCONbits.TMR0IE = 1; // タイマー割り込み開始!! T0CON |= 0b10000000; // ------------------------------ // ここまで // ------------------------------ // ステータスLED点灯 PORT_STATUS_LED = LED_ON; // ------------------------------ // 距離測定タイマースタート // ------------------------------ // タイマー1カウンタを初期化 timer1_count = 0; // タイマー値設定 TMR1H = timer1_h; TMR1L = timer1_l; // タイマー割り込みフラグ初期化 PIR1bits.TMR1IF = 0; // タイマー1割り込み許可(=1) (Timer1 Overflow Interrupt Enable bit ... PIE1) PIE1bits.TMR1IE = 1; // タイマー割り込み開始!! T1CON |= 0b00000001; // HC_SR04のTrigポートをLow(=0)にする PORT_HC_SR04_TX = 0;
すると、HC-SR04のEchoポートが少ししてからHighになるので、これをPICが
検知して状態変化割り込みが発生するので、
・HC-SR04のEchoポートのデータを取得
・EchoポートがHighなら
・距離測定タイマーの値をリセット
・そうではなく、EchoポートがLowなら
・状態変化割り込み禁止
・タイムアウトタイマー停止
・距離測定タイマー停止
・状態変化割り込みフラグ初期化
(どっちにしても状態変化割り込みが発生したらクリアすること)
// ---------------------------------------- // 状態変化割り込み // ---------------------------------------- // 状態変化割り込み(=1)なら (Port B Interrupt-On-Change (IOCx) Interrupt Flag bit ... RBIF) if (INTCONbits.RBIF == 1) { // HC-SR04のEchoポートのデータを取得 temp_buffer = PORT_HC_SR04_RX; // EchoポートがHighなら if (temp_buffer == 1) { // タイマー1カウンタを加算 timer1_count = 0; // タイマー値設定 TMR1H = timer1_h; TMR1L = timer1_l; } // そうではなく、EchoポートがLowなら else { // 状態変化割り込み禁止 INTCONbits.RBIE = 0; // ------------------------------ // タイムアウトタイマー停止 // ------------------------------ T0CON &= 0b01111111; // ステータスLEDをOFF PORT_STATUS_LED = LED_OFF; // ------------------------------ // 距離測定タイマー停止 // ------------------------------ T1CON &= 0b11111110; // 距離測定タイマーのリザルトLEDもOFFに PORT_RESULT_LED = LED_OFF; // タイマー0カウンターリセット timer0_count = 0; // 距離測定モードをEND(=99)に ping_mode = 100; } // 状態変化割り込みフラグ初期化 INTCONbits.RBIF = 0; }
として、Lowになったときに終了フラグをセットするなどして、メインループ
の方で終了フラグにより距離測定タイマーの値を読み出せば、2usでカウント
しているので割り算することなく、HC-SR04から対象物までの片道の音速での
経過時間となる。
あとはWikiPediaとかにも書いてあるけど、計算式にぶち込めば対象物までの
距離が計算できる。
http://ja.wikipedia.org/wiki/音速
ちなみに手持ちのHC-SR04での測定例をあげると
定規 測定 誤差
5cm 4.2cm 約0.8cm
10cm 9.5cm 約0.5cm
15cm 14.1cm 約0.9cm
(20℃として計算)
なので、結構いい加減と思うか正確と思うか!?は皆さん次第です。
まぁ4000円近いものが100円そこそこになるんだから、自分だったら誤差には
目をつぶって使用方法を工夫して誤差をカバーかな? 🙂