超音波センサHC-SR04の使い方|ArduinoとRaspberry Piで解説

超音波センサの仕組みを解説
なんで超音波で距離が測定できるの?

超音波で距離が測定できる仕組みを説明しますね。
そもそも超音波とは、人間の耳には「聞こえない」または「聞こえにくい」音域の音のことです。とはいえ聞こえないだけであって音にはかわりありません。
超音波も音も、空気を振動させて遠くへ広がっていきます。そして、その音の速さは決まっているのです。(ココ大事)
音の速さは、気温や気圧によっても変化しますが、たとえば1気圧で20度のときでは「秒速343.7m/s」であることが知られてます。
よって、音の速さが分かっているのであれば「音を出してからその音が戻ってくるまでの時間」を測れば距離が測定できます。
つまり距離の計算式は次のようになります。
\[ 距離 = 音速 \times \frac{往復時間}{2} \]
音速の定義
また、音の速さを正確に知りたい場合は、次式で計算できます。ただし、この式は「1気圧で空気が乾燥している」という条件が付きます。また、t
は摂氏温度、音速の単位はm/sです。
\[ 音速 = 331.5 + 0.61t\]
距離の計算方法
これらの式を元に、たとえば気温を20度と仮定して、超音波を発信してから受信するまでの往復時間をT秒
としてみましょう。距離の計算は次のとおりになります。ただし、距離の単位はメートルです。
\[ 距離 = 331.5 + 0.61 \times 20 \times \frac{T}{2} \]
ところで、音は高域(周波数が高い)になるほど直進する性質を持ってます。これを「指向性」と呼んだりします。この「指向性」をもった音のほうが、狙った方向に向かって進んでくれるので、距離を測定したいときなどに便利なんですね。
ArduinoでHC-SR04を使う
ここからはArduinoと超音波センサ(HC-SR04)を使った距離測定のやり方を解説します。HC-SR04は、超音波の送信機と受信機が一体になったセンサモジュールです。信号の送受信はHC-SR04が自動でやってくれます。ですから、超音波センサの扱いはとても簡単なのです。Arduino初心者の方でも恐れずに超音波センサで遊んでみてください。
HC-SR04の使い方は、次の3つのポイントを理解するだけです。
- Triggerピンを10μ秒だけHighにすると、超音波が発信される
- ❶の発振が終わったタイミングでEchoピンがHighになる
- 跳ね返ってきたパルスを受信するとEchoピンがLowになる
この手順を図で表現すると次のようになります。

距離を測るには、EchoピンがHighからLowに変わるまでの時間だけを知れればよいのです。具体的には、❷から❸の往復時間をArduinoなどで監視することになります。
ちなみに、超音波パルスの周波数は40kHzになります。HC-SR04の詳細はこちらをご覧ください。 http://akizukidenshi.com/download/ds/sainsmar/hc-sr04_ultrasonic_module_user_guidejohn_b.pdf
HC-SR04とArduinoの配線
ここからは実際に、HC-SR04とArduinoを使っていきます。まずは配線です。HC-SR04とArduinoの端子をそれぞれ表のように接続してください。
HC-SR04 | Arduino |
---|---|
Vcc | 5V |
Trig | D1 |
Echo | D2 |
GND | GND |
ただし、お使いのArduinoによっては、次のように端子の電圧に気をつけなければなりません。
Seeeduino XIAOで使う場合の注意点
Seeeduino XIAOのGPIOピンと、HC-SR04のEchoピンは直接接続してはダメです。なぜなら、HC-SR04のEchoピンからは5Vの信号が出力されるからです。
Seeeduino XIAOの場合、入力は3.3Vまでです。ですので5V信号を3.3V電圧に変換する必要があります。
簡単な方法として、抵抗を使った分圧法があります。次の図のように、抵抗を2本つかって5V電圧を3.3Vに変換できます。

HC-SR04のTriggerピンは、入力になります。3.3V〜5Vの電圧を入力できますので、レベルシフトの必要はありません。Seeeduino XIAOのGPIOをそのまま接続できます。
ここら辺は、デジタルピンに使われている「MOSFETの仕組み」を理解すると良いです。余裕のある方は「MOSFETの使い方」をご覧ください。
距離を測定するソースコード
配線ができましたら、Arduinoをプログラミングしてみましょう。
Arduino IDEでSketchファイルを新規作成し、つぎのプログラムを書き込んでみましょう。Arduino IDEのシリアルモニターを開いてみてください。距離(cm)が表示されているはずです。超音波センサを動かして、障害物などに向けると距離の値が変化します。
/*
Created by Toshihiko Arai.
https://101010.fun/arduino-hc-sr04.html
*/
#define TrigPin 1 // D1
#define EchoPin 2 // D2
double speedSound = 331.5 + 0.61 * 20; // 20は現在の気温
double distance = 0;
void setup() {
.begin(9600);
Serial(TrigPin, OUTPUT);
pinMode(EchoPin, INPUT);
pinMode}
void loop() {
();
trigger
double t = pulseIn(EchoPin, HIGH); // μS
if (t > 0) {
= t / 2; //往復距離なので半分の時間
t = t * speedSound * 100 / 1000000; // 距離(cm)を計算
distance .println(distance);
Serial}
(500);
delay}
void trigger() {
(TrigPin, LOW);
digitalWrite(2);
delayMicroseconds(TrigPin, HIGH );
digitalWrite( 10 );
delayMicroseconds(TrigPin, LOW );
digitalWrite}
ところで、プログラム中のpulseIn
は「デジタルピンがHigh状態になっている時間をマイクロ秒で返す」とても便利な関数です。Arduino言語では標準で使える関数になります。
【発展】ArduinoとOLEDで距離を表示させてみた
少し発展としてシリアルモニターではなく「OLEDディスプレイ」へ距離を表示してみました。
距離を表示するソースコード
さきほどのプログラムを元に、OLEDで文字表示できるように改良してみました。
/*
Created by Toshihiko Arai.
https://101010.fun/arduino-hc-sr04.html
*/
#include <U8g2lib.h>
(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2
#define TrigPin 1 // D1
#define EchoPin 2 // D2
double speedSound = 331.5 + 0.61 * 20; // 20は現在の気温
void setup() {
.begin(9600);
Serial(TrigPin, OUTPUT);
pinMode(EchoPin, INPUT);
pinMode
.begin();
u8g2}
void loop() {
();
trigger
double distance = 0;
double t = pulseIn(EchoPin, HIGH); // μS
if (t > 0) {
= t / 2; //往復距離なので半分の時間
t = t * speedSound * 100 / 1000000; // 距離(cm)を計算
distance (distance);
displayOLCD}
(500);
delay}
void trigger() {
(TrigPin, LOW);
digitalWrite(2);
delayMicroseconds(TrigPin, HIGH );
digitalWrite( 10 );
delayMicroseconds(TrigPin, LOW );
digitalWrite}
void displayOLCD(double distance) {
char buf[10];
(buf, 10, "%.1fcm", distance);
snprintf
.clearBuffer();
u8g2.setFont(u8g2_font_crox3hb_tf);
u8g2.drawStr(0, 16, "Distance");
u8g2.drawStr(0, 32, buf);
u8g2.sendBuffer();
u8g2}
プログラム中のu8g2.drawStr
は、char型で文字を渡さなければなりません。そのため、snprintf
関数でdouble値をchar型へ変換させてます。
対象物との距離を5cm・10cm・20cm・30cmと変えて測定してみました。写真のように、かなり正確な距離を測定できて驚きです。
Raspberry PiでHC-SR04を使う
HC-SR04とRaspberry Piの配線

HC-SR04 | ラズパイ |
---|---|
Vcc | 5V |
Trig | GPIO20 |
Echo | 電圧レベル変換後GPIO21⭐︎ |
GND | GND |
Echoピンの電圧レベル変換⭐︎
HC-SR04のEchoからは5Vの信号が出力される。しかし、Raspberry PiのGPIOの入力電圧は3.3Vまで。そのため直接繋ぐことはできない。今回は、3.3Vのツェナーダイオードを使って5Vを3.3Vへ変換させることにした。

他にも抵抗で分圧したりレベルシフターを使うといった手段があります。
距離を測定するソースコード
Raspberry
PiでHC-SR04
を使って距離を測定するプログラムがこちら。
import RPi.GPIO as GPIO
import time
import os
import signal
GPIO.setmode(GPIO.BCM)= 20
TRIG = 21
ECHO = 343 # 気温20度の時の音速(m/s)
C
GPIO.setup(TRIG, GPIO.OUT)
GPIO.setup(ECHO, GPIO.IN)0)
GPIO.output(TRIG, 0.3)
time.sleep(
def readDistance():
1)
GPIO.output(TRIG, 0.00001) # 10μs
time.sleep(0)
GPIO.output(TRIG,
while GPIO.input(ECHO) == 0:
= time.time()
signaloff while GPIO.input(ECHO) == 1:
= time.time()
signalon
= signalon - signaloff
t = t * C * 100 / 2
distance return distance
def cleanup():
print('cleanup')
GPIO.cleanup()
try:
while True:
= readDistance()
dist print(dist)
0.1)
time.sleep(
except KeyboardInterrupt:
# SIGINTを監視していれば不要
print('KeyboardInterrupt')
except:
print('other')
finally:
# 終了処理
cleanup()
【発展】Raspberry Piと超音波センサで物体の追跡
ここからはRaspberry
Piを使って超音波センサとサーボモータを組み合わせた物体の追跡方法を解説していく。超音波センサはHC-SR04
、サーボモータはSM-S2309S
を使用した。
「超音波センサ1つで物体に追跡させる」ことを考えてみた。
超音波センサモジュール1つで、物体を追跡させるには少し工夫しなければならない。なぜなら、人間にたとえると「片耳だけ」で方向を探知するようなものだからだ。
超音波センサモジュールを見ると、2つの目があるように見えるが実は「1つ目」である。というか「口と耳」なのだ。1つは超音波を発する「口」、そしてもう1つは自分が発した超音波を受信する「耳」である。
サーボモータの動かし方

物体の動きに合わせて首を振るようにしたいので、ここではサーボモータの使い方を説明する。何かのキットに付いていたサーボモータSM-S2309S
を使ったが、他のサーボモータでも問題ないと思う。
サーボーモータの動かし方のポイントは次の2つだ。
サーボモータとRaspberry Piの配線

ラズパイ | サーボモータ |
---|---|
GND | GND |
GPIO26 | Signal |
ラズパイ以外の5V電源 | 5V |
サーボモータの電源はRaspberry Piと別にしよう
サーボモータの電源はラズパイから拾うのではなく、別電源を用意する。なぜならモータの消費電力は大きいので、動作が不安定になるからだ。
サーボモータの動作テストプログラム

これを元に、サーボモータの動作テストプログラムをPythonで書いてみた。
import RPi.GPIO as GPIO
import time
import os
import signal
GPIO.setmode(GPIO.BCM)= 26
SIG
GPIO.setup(SIG, GPIO.OUT)
= GPIO.PWM(SIG, 50)
servo 0.3)
time.sleep(
0)
servo.start(6.3) # 0°
servo.ChangeDutyCycle(1.0)
time.sleep(
for i in range(5):
2.2) # 0°
servo.ChangeDutyCycle(1.0)
time.sleep(
10.8) # 180°
servo.ChangeDutyCycle(1.0)
time.sleep(
6.3) # 90°
servo.ChangeDutyCycle(1.0)
time.sleep(
servo.stop() GPIO.cleanup()
超音波センサ1つで物体を追跡する仕組み
それでは本題の「超音波センサ1つで物体を追跡」をやってみよう。
1つの超音波センサモジュールだけで、物体の追跡をさせる方法は次の通り。
- 物体を見つけたら常にその右端または左端を狙うようにする
- ごくわずかの角度だけ常に首を振って「見つけた」「見つけていない」を高速で繰り返す


もし物体が移動して見失ってしまったら、すぐに見つけられるよう首を振るスピードを速める。つまりサーボモータの角速度を大きくする。こうすることにより、物体の端を捉えることができるはずだ。
ソースコード
これらの考えを元に、組んだPythonプログラムがこちらである。条件分岐が多く読みにくいプログラムとなってしまったが悪しからず。
import RPi.GPIO as GPIO
import time
import os
import signal
# 役割ピン番号で命名
GPIO.setmode(GPIO.BCM) = 20
TRIG = 21
ECHO = 26
SIG = 2.2 # 0° 時計の針で9時を0°とする
SERVO_PWM_MIN = 10.8 # 180°
SERVO_PWM_MAX = (SERVO_PWM_MAX - SERVO_PWM_MIN) / 180
SERVO_PWM_1DEGREE = 20 # 最短距離
MONITORING_DIST
= 343 # 気温20度での音速(m/s)
C
GPIO.setup(TRIG, GPIO.OUT)
GPIO.setup(ECHO, GPIO.IN)
GPIO.setup(SIG, GPIO.OUT)
0)
GPIO.output(TRIG, = GPIO.PWM(SIG, 50)
servo 0.3)
time.sleep(
0)
servo.start(
def readDistance():
1)
GPIO.output(TRIG, 0.00001) # 10μs
time.sleep(0)
GPIO.output(TRIG,
while GPIO.input(ECHO) == 0:
= time.time() # 秒
signaloff while GPIO.input(ECHO) == 1:
= time.time() # 秒
signalon
= signalon - signaloff # 秒
t = t * C * 100 / 2
distance return distance
def rotate(angle): # 0°から180°
= angle * SERVO_PWM_1DEGREE + SERVO_PWM_MIN
pwm if pwm > SERVO_PWM_MAX:
= SERVO_PWM_MAX
pwm elif pwm < SERVO_PWM_MIN:
= SERVO_PWM_MIN
pwm
servo.ChangeDutyCycle(pwm)return
def cleanup():
print('cleanup')
90)
rotate(1)
time.sleep(
servo.stop()
GPIO.cleanup()
= 0
initAngle = 0
currentAngle = True
clockwise = False
isCatched = False # 物体を見失う
isMissing = False # 不安フラグ
isWondering = 0
missingCount
try:
rotate(initAngle)1)
time.sleep(while True:
= readDistance()
dist # print('角度: {0}, 距離: {1}'.format(currentAngle, dist))
= dist < MONITORING_DIST
isExistObject
if isExistObject:
= False
isMissing = 0
missingCount = False
isWondering if isCatched == False: # 新規発見!
= True
isCatched = False if clockwise == True else True
clockwise else:
if isCatched:
= False
isCatched = False if clockwise == True else True
clockwise # print("物体を見逃しますた!")
= True
isMissing
if isMissing:
+= 1
missingCount
if missingCount > 100: # 物体が存在しなかったのでリセットする
= False
isCatched = False
isWondering = False
isMissing elif missingCount > 10: # 不安フラグを立てる
= True
isWondering
if isCatched:
= 0.7
gain elif isWondering:
= 4
gain else:
= 1
gain
if clockwise:
*= 1
gain else:
*= -1
gain
+= 1 * gain
currentAngle
if currentAngle > 180:
= False
clockwise elif currentAngle < 0:
= True
clockwise
rotate(currentAngle)0.005)
time.sleep(
except KeyboardInterrupt:
print('KeyboardInterrupt')
except:
print('other')
finally:
cleanup()
改善点としては、動きが速すぎると物体を見失ってしまうので、首振りの部分を工夫する必要がある。
関連記事
- 【XYペンプロッター制作⑦】サーボモータでペンを上下させる(仮完成)
- ArduinoでマイクロサーボモータSG90の使い方
- Arduinoと可変抵抗でLEDの明るさ制御
- ESP32のマルチスレッドで複数のサーボモータを同時に動かす方法
- 【Raspberry Pi】BMX055でジャイロ・加速度の測定