超音波距離センサーは以前から使ってみたいと思いつつ手を出していなかったデバイスです。今 MIDI/CV の製作が止まってしまっているのでその間数日でできるプロジェクトをやっています。これはその一つ。基本的にデータシートどおりに使って距離を測っただけですけど内容を忘れてしまわないうちに記事にしておきます。
センサの入手
超音波センサなら素直に秋月電子通商から買えばよいのですが Ali Express に非常に安価なものが出回っているのでそちらを試してみようと 、HC-SR04 というセンサを4個調達。Ali Express からの部品にはデータシートはついていないので、型番から探すことになります。HC-SR04 にはいくつか版があるみたいですが基本的な作りと使い方は一緒で見つかったものをそのまま当てれば良いようです。入手したものの版には見つかったデータシートにない機能としてインタフェースを GPIO 以外に I2C、UART、I-Wire と切り替えられるようです。I2C と UART のインタフェースがどんなかは別版のデータシートにあったので、あとは基板を読んで察してね、というノリで出回っているようです。まあ安上がり。

超音波距離センサはどんな風に動くのか
センサにはトランスデューサとレシーバがついていて、トランスデューサから送った超音波が対象に当たって跳ね返った音波をレシーバが受け取り、その時間差を測定すると距離がわかる、という仕組みです。センサには周辺回路がついており、音波の送受信や受信した音波の頭の部分の検知など細々した実装の面倒は周辺回路がやってくれます。当然音波のプロトコルはセンサの周辺回路まかせで変えられないので使い方が簡単な反面融通は利きません。もっとあれこれやりたくなったらどうするかはおいおい考えるとして、まずは使ってみて感触をつかみたいと思います。
データシートに載っていた使い方の説明図は以下のとおり

センサには入力端子と出力端子があって、入力端子に10μS 以上のパルスを入れると40kHz の超音波が放出されてそれと同時に出力端子のレベルが H になる、レシーバが超音波信号を受け取ったら出力レベルが L に落ちるという仕組みで、出力パルスの時間幅を音速で割って距離を求めてね、という仕組みです。データシートには気温ごとの音速が乗っています。今回はとりあえず 20℃ の数値を使いました。
ちなみにデータシートは中国語で、英語のものも見つかりましたが古い版のものでした。中国語のデータシートは自動翻訳に一旦かけて大筋を頭に入れたらあとは漢字の意味を推測してなんとか読めました。翻訳したものをそのまま読んでもいいのですけど図は翻訳されないしフォーマットは崩れるので原文のほうが見やすいです。
PSoC を使ってセンサを読み取り
手元にあるプロセッサは今 PSoC しかないのでそれを使わざるを得ないのですが PSoC はプロトタイピングにとても向いています。欲しいペリフェラルをよいしょっと置いてぱちぱちっとつないで少しのコードを書くだけです。モジュールの設定は動作中変えないのならコードに書く必要もありません。ほんとに楽ちん。今回は、PWM を使って定期的にセンサの起動パルスを発生、出力パルスの幅はタイマを使って読みます。ハードウェア構成はこんな感じ

トリガー部分は非常に単純、定期的にパルスを発生するだけです。コードではモジュールをスタートさせるだけで他に何も書く必要がありません。受信部はもう少し複雑、パルスの立ち上がりによってタイマに trigger をかけ、立下りによって capture します。タイマのクロック周波数を工夫して、1カウント 1mm となるように設定しました。ペリフェラルのクロック周波数を任意に選べるのが PSoC のもう一つの強みです。capture のタイミングで割り込みがかかるように設定し、割り込みハンドラでは capture したタイマ値を7セグメント表示器で表示します。コードは以下のような感じ、表示の単位は cm です。
#include "project.h"
// Interrupt handler declarations
CY_ISR_PROTO(TimerCaptureHandler);
int main(void)
{
CyGlobalIntEnable; /* Enable global interrupts. */
// Initialization ////////////////////////////////////
LED_Driver_1_Start();
isr_RPM_StartEx(TimerCaptureHandler);
Timer_1_Start();
Timer_1_WriteCounter(0);
PWM_Invocations_Start();
Timer_1_WriteCounter(65535);
Timer_1_EnableTrigger();
Timer_1_Start();
for(;;)
{
Pin_LED_Write(Pin_Echo_Read());
}
}
// static float sum_interval = 0;
static const float alpha = 0.5;
CY_ISR(TimerCaptureHandler)
{
Timer_1_Stop();
uint16_t value = 65535 - Timer_1_ReadCapture();
LED_Driver_1_Write7SegNumberDec(value / 10, 0, 3, LED_Driver_1_RIGHT_ALIGN);
Timer_1_STATUS;
Timer_1_WriteCounter(65535);
Timer_1_EnableTrigger();
Timer_1_Start();
}
C実験
今回はデータ取ったりなどはせずただ動くことを確認しました。
オシロスコープでは出力パルスの波形を確認、一定幅のパルスが出ているところが見えています。精度は -3% ぐらい、ざっと組んだ割には正確です。
こちらは、横切る障害物を検知する様子です。
近接したものを検知する感度は高そうですが、障害物の方向は当然わかりません。指向性もだいぶん鋭そうです。あと、音波のプロトコルが単純なのでセンサを複数動かすとおそらく正常に動作しないと思います。センサを起動するタイミングをずらせば良いかもしれませんがデータシートによると起動は 200ms おきにしておく必要があり、これを無視して間隔を短くしても 100ms は切れないと思います。これで10台同時に使うとセンサごとの周期は 1s となってしまい、これは遅すぎるなあ。何か非同期にセンサを動かす工夫はできないか考えたいです。こんな風にここからもう一歩踏み出すにはセンサのファームウェアを書き換えないといけませんが IC の刻印はないか消えるかしていてどんなチップを使っているのかわかりません。わかっても専用 IC で中身の書き換えは無理なのではと予想しています。それとは別に、周辺回路のアナログ部がどうなっているか、トランスデューサやレシーバ上の波形がどうなっているかなども、もう少し詳しく知りたいです。