M5Stackで始めるセンサ・インターフェーシング (7) 超音波距離センサを利用
電子工作で距離を測るのに最も使われているのが、超音波距離センサです。丸っこい超音波センサ(マイクとスピーカ)を二つ使うので、ロボットの目のようでとても雰囲気があります。市販品を購入すると、ほとんど同じ外観でも複数あることがわかりました。
- 信号 Trig、Echo、OUT(写真上。アマゾンで入手)
- 信号 Trig、Echo
- 信号 SIG(写真下。Grove - Ultrasonic Ranger V2.0)
- 電圧 5V専用
- 電圧 3.3/5V両方可
超音波とは、すごい音波のことではなく周波数が40kHz付近のことを指します。人が発する音声は2~5kHz、聞こえるのは20kHzまでが多いようです。コウモリは、キャリア周波数が数十kHzから100kHzの超音波パルスを発するそうです。メガネの洗浄器にもこの40kHzが使われます。歯石除去器の超音波スケーラにも使われます。
片方の超音波スピーカから超音波を出し、もう一方の超音波マイクで受信する仕組みなので、二つの目玉のように見えます。
音波を発してから物体に反射し、戻ってきた音波を受信する時間を測って距離を得るのは、ToF(Time of Flight)と呼ばれています。最近の距離センサは光(レーザー)が主流で、この連載の1回目でVL53L1Xを使いました。
●Grove - Ultrasonic Ranger V2.0のおもなスペック
Seeedの製品Grove - Ultrasonic Ranger V2.0は、電源以外に信号はSigのみです。Groveコネクタは4ピンで、1ピンは何もつながっていないNCです。
- 動作電圧 3.2~5.2V
- 消費電流 8mA
- 周波数 40kHz
- 解像度 1cm
- 出力 PWM
- 測定範囲 15度
- トリガ・パルス 10us
●接続
Ultrasonic Ranger |
M5Stack |
---|---|
GND | GND |
Vcc | 3V3 |
NC | - |
SIG | 21番 |
●ライブラリの導入
ライブラリ管理の検索欄にultrasonicと入力して、たくさん表示された中からGrove Ultrasonic Rangerをインストールします。
スケッチ例からUltrasonicDisplayOnTermを読み込みます。
デフォルトでは、7番ピンにSIGピンをつなぐ記述なので、
Ultrasonic ultrasonic(21);
と21番に修正し、インチ表示部分をコメントアウトしてからコンパイル、実行します。センサの前に手のひらをかざしてみたら、正しいそうな値を表示しています。
●波形
センサの前25cmぐらいに手をかざしたとき。
155cmぐらいのとき。
パルスを出してから戻ってくるまで8.997msかかっています。往復なので半分にします。音速は温度(20℃とした)の関数なので、
8.997 / 2 * (331.5 + 0.6 * 20) / 1000000 * 100 * 1000 = 154.5[cm]
シリアルモニタの表示は155もしくは154cmでした。
●スケッチを独自に作る
21番を出力にして10usのパルスを発信します。すぐに入力に切り替え、LOWになる時間をpulseIn()で読み取ります。
実行時の波形です。ライブラリを利用したときと同じです。
スケッチです。音速は340m/secで近似しています。
const int SigPin = 21;
long duration;
int distance;
void setup() {
Serial.begin(9600);
}
void loop() {
pinMode(SigPin, OUTPUT);
digitalWrite(SigPin, LOW);
delayMicroseconds(2);
digitalWrite(SigPin, HIGH);
delayMicroseconds(10);
digitalWrite(SigPin, LOW);
pinMode(SigPin, INPUT);
duration = pulseIn(SigPin, HIGH);
distance= duration*0.034/2;
Serial.println("Distance: " + String(distance) + " cm");
delay(200);
}
●三つの信号Trig、Echo、OUTがあるモデル
この超音波距離センサはArduino用として販売されているので、動作電圧は5Vです。Trigが発振用パルスを出す端子で、Echoが受信用の端子と思われます。オシロスコープで波形を観測します。Echo端子は5V出力です。M5StackのGPIOは3.3V用なので、直接つないではいけません。
OUT信号は変化しなかったので、何に使うか不明です。
信号のレベル変換は専用の変換回路が望ましいです。GPIOの入力抵抗値が不明ですが、抵抗で分圧して実験的に下記の抵抗値で必要なHIGHとLOWの電圧を得られました。
Ultrasonic Ranger |
M5Stack |
---|---|
Vcc | 5V |
Trig | 21番 |
Echo | 抵抗分割回路を通して22番 |
OUT | - |
GND | GND |
スケッチです。
const int trigPin = 21;
const int echoPin = 22;
const int OutPin = 23;
long duration;
int distance;
void setup() {
Serial.begin(9600);
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
}
void loop() {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
distance= duration*0.034/2;
Serial.println("Distance: " + String(distance) + " cm");
delay(200);
}
実行中の様子です。
●GUIで見やすく
Grove - Ultrasonic Ranger V2.0を使って距離を棒グラフで表示します。常に最新のデータを表示し、過去のデータも見れるように、横スクローリングをします。
j<16のif文は画面いっぱいに16個の測定した棒グラフを描きます。
x0>=319のif文は、右端に最新の棒グラフを描き、その間、それまで描画したグラフを左にシフトさせています。二つのルーチンで棒の色を変えていますが、特に意味はありません。
読み取った距離データのdistanceはグローバル変数なので、readDistance()でreturnする値を使っているわけではありません。この関数内でLCDへの表示をしていますが、あまり勧められた処理ではなく、本来、loop()側で戻り値を使って表示すべきだと思います。
#include <M5Stack.h>
TFT_eSprite graph1 = TFT_eSprite(&M5.Lcd); // Sprite object graph1
int x0=0;
int j=0;
const int SigPin = 21;
long duration;
int distance;
void setup() {
Serial.begin(9600);
M5.begin();
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(YELLOW);
M5.Lcd.setCursor(10, 10);
M5.Lcd.print("Ultrasonic distance start");
graph1.setColorDepth(8);
graph1.createSprite(320, 240);
graph1.fillSprite(BLACK);
graph1.pushSprite(0, 80);
}
void loop() {
graph1.pushSprite(0, 80);
x0 += 20;
if (j<16) {
j += 1;
readDistance();
graph1.fillRect(j*18, 160-distance, 18, distance, TFT_ORANGE);
delay(1000); // wait so things do not scroll too fast
graph1.scroll(-1, 0);
}
if (x0>=319) {
x0=0;
readDistance();
graph1.fillRect(305, 160-distance, 20, distance, YELLOW);
graph1.scroll(-1, 0); // scroll graph 1 pixel left, 0 up/down
}
graph1.scroll(-1, 0);
delay(50);
}
int readDistance(){
pinMode(SigPin, OUTPUT);
digitalWrite(SigPin, LOW);
delayMicroseconds(2);
digitalWrite(SigPin, HIGH);
delayMicroseconds(10);
digitalWrite(SigPin, LOW);
pinMode(SigPin, INPUT);
duration = pulseIn(SigPin, HIGH);
distance= duration*0.034/2;
Serial.println("Distance: " + String(distance) + " cm");
M5.Lcd.fillRect(1, 30, 300, 44, BLACK);
M5.Lcd.drawRightString(String(distance), 100, 30, 4);
M5.Lcd.drawCentreString(" cm", 115, 40, 2);
return distance;
}
実行中の様子です。最大150cmぐらいまでを正しく表示します。
ガラスの窓はその面までの距離が測れます。黒いスピーカの筐体はあいまいな距離しか測れませんでした。