【HC-SR04】ラズパイと超音波距離センサでレーダーを作る

2024/02/22

HC-SR04 サーボモーター ラズパイ

  • B!
サムネ
サムネ

今回は、超音波によって距離を測定するセンサ「HC-SR04」を使って周囲の障害物の状態を把握するレーダーを作成していきます。

今回使用するもの

  • RaspberryPi
  • HC-SR04
  • サーボモーター(SG90)
  • ジャンプワイヤ

HC-SR04の測定原理

センサーの挙動

  1. 片方のスピーカーから一瞬だけ超音波を発する
  2. 超音波が障害物に衝突し、再び戻ってくる
  3. 戻ってきた超音波をもう片方のスピーカーで拾う
  4. 発した超音波が返って来たことを認識する

センサーは上記の動作しか行っていません。

よってこれらをプログラムによって距離に変換する作業が必要です。

時間情報の取得

  1. 超音波を発する => 時刻を取得
  2. 戻ってきた超音波をもう片方のスピーカーで拾う => 時刻を取得
  3. 時刻②-時刻①で超音波が移動した時間を求める

これで、「時間」を得ることができました。

「時間」から「距離」に変換

空気中を伝わる超音波は音速で移動します。

音速は環境によって若干変動しますが大体340m/sです。

音速と時間があれば距離に変換することができます。

ここで注意するのはHC-SR04で記録した時間は「往復時間」であることです。

センサーから障害物までの距離は1往復分の時間がかかっているので2で割ります。

  1. 距離(m) = 取得した時間(s) * 340(m/s) / 2

メートル(m)ではなく、センチメートル(cm)で出力したい場合は次のようにします。

  1. 距離(cm) = 取得した時間(s) * 34000(cm/s) / 2

以上の処理を行うことでHC-SR04で距離を測定することが可能になるわけです。

1.距離を測ってみる

HC-SR04の測定範囲は0.02m~4mなので、それ以外の値は範囲外として扱います。

hc-sr04.py
  1. import RPi.GPIO as GPIO
  2. import time
  3. import sys
  4.  
  5. #GPIOの番号
  6. Trig = 14
  7. Echo = 15
  8.  
  9. GPIO.setmode(GPIO.BCM)
  10. GPIO.setup(Trig, GPIO.OUT)
  11. GPIO.setup(Echo, GPIO.IN)
  12.  
  13. def read_distance():
  14. #一瞬超音波を発する
  15. GPIO.output(Trig, GPIO.HIGH)
  16. time.sleep(0.00001)
  17. GPIO.output(Trig, GPIO.LOW)
  18. #超音波を発した瞬間はLOW => 時間の記録
  19. while GPIO.input(Echo) == GPIO.LOW:
  20. sig_off = time.time()
  21. #超音波が帰ってきたらHIGH => 時間の記録
  22. while GPIO.input(Echo) == GPIO.HIGH:
  23. sig_on = time.time()
  24. #LOWからHIGHになるまでの時間を計算
  25. duration = sig_on - sig_off
  26. #距離を算出
  27. distance = duration * 340 / 2 #cm 340m/s 34000cm/s
  28. return distance
  29.  
  30. while True:
  31. try:
  32. dis = read_distance()
  33. #算出した距離が計測範囲内かどうか
  34. if dis > 0.02 and dis < 4: #0.02m~4m
  35. dis = round(dis, 3)
  36. print(dis, 'm')
  37. else:
  38. print('Out Of Range', round(dis, 3),'m')
  39. time.sleep(0.5)
  40. except KeyboardInterrupt:
  41. GPIO.cleanup()
  42. print('CleanUp')
  43. sys.exit()

実行結果

実行結果

2.超音波レーダーの構想

距離が分かるということは障害物があるということです。

センサーを動かしながら取得した距離をグラフ上にプロットすれば、周囲の障害物情報を可視化することができそうです。

センサーの動かし方は次の2通りあります。

  • 一定の方向に直前移動させる
  • センサーの位置を固定して回転させる

直前移動レーダー

この方法ではセンサーの移動方向をX軸、距離情報をY軸にプロットします。

レーダー

180°回転レーダー

この方法ではセンサーの位置は固定(0,0)し、回転角度と距離情報を用いてプロットします。

三角関数を用いてプロットする(x,y)の値を計算します。

この方法では一方向のみに超音波を発するのではなくサーボモーターの回転角である180°の範囲を検出できます。

今回はサーボモーターを使って180°回転レーダーを作成していきます。

プロット位置の算出方法

例えばサーボモーターを1°ずつ回転させて距離を測っているとします。

15°の時に1.5mが得られたときxとyの計算はそれぞれ次のようになります。

  1. x = 1.5 * cos15
  2. y = 1.5 * sin15

このようにθが鋭角の時には次の式を用いることができます。

三角関数


150°の時に2mが得られたときのxとyは次のようになります。

  1. x = -(2 * cos(180-150))
  2. y = 2 * sin150

このようにθが鈍角の時は次の式を用いることができます。

三角関数


最後にθ = 0, 90, 180の時のxとyの値についてです。

それぞれ次のようになります。

  1. distance : 取得した距離
  2.  
  3. θ=0
  4. x = distance
  5. y = 0
  6. θ=90
  7. x = 0
  8. y = distance
  9. θ=180
  10. x = -distance
  11. y = 0

以上より、回転角度θの値によって条件分岐し、使う式を切り替えることでプロットする値を計算することができます。

作成したプログラム

ここまでの内容をもとに作成したプログラムです。

距離を取得するたびにサーボモーターを1°回転させます。

サーボモーターの回転角は180°ですが、プログラム上では+90°~-90°として扱う必要があります。

theta(θ)」は0~180°、「angle」は-90~+90°となっています。

graph_radar.py
  1. import RPi.GPIO as GPIO
  2. import time
  3. import sys
  4. import RPi.GPIO as GPIO
  5. import math
  6. import csv
  7.  
  8. #GPIOの設定
  9. Trig = 14
  10. Echo = 15
  11. Servo_pin = 13
  12. GPIO.setmode(GPIO.BCM)
  13. GPIO.setup(Servo_pin, GPIO.OUT)
  14. GPIO.setup(Trig, GPIO.OUT)
  15. GPIO.setup(Echo, GPIO.IN)
  16. Servo = GPIO.PWM(Servo_pin, 50)
  17. Servo.start(0)
  18.  
  19. #距離の算出
  20. def read_distance():
  21. GPIO.output(Trig, GPIO.HIGH)
  22. time.sleep(0.00001)
  23. GPIO.output(Trig, GPIO.LOW)
  24. while GPIO.input(Echo) == GPIO.LOW:
  25. sig_off = time.time()
  26. while GPIO.input(Echo) == GPIO.HIGH:
  27. sig_on = time.time()
  28. duration = sig_on - sig_off
  29. distance = duration * 340 / 2 #cm 340m/s 34000cm/s
  30. return distance
  31.  
  32. #サーボモーターを回転させる
  33. def servo_angle(angle):
  34. duty = 2.5 + (12.0 - 2.5) * (angle + 90) / 180
  35. Servo.ChangeDutyCycle(duty)
  36. time.sleep(0.3)
  37.  
  38. while True:
  39. try:
  40. angle = -90
  41. theta = 0
  42. while True:
  43. print('\ntheta', theta)
  44. print('angle', angle)
  45. servo_angle(angle)
  46. dis = read_distance()
  47. if dis > 0.02 and dis < 4: #2cm~4m
  48. #dis = round(dis, 3)
  49. print(dis, 'm')
  50. #print(theta)
  51. else:
  52. print('Out Of Range')
  53. #print(theta)
  54. theta = theta + 1000
  55.  
  56. if theta == 0:
  57. x = dis
  58. y = 0
  59. elif theta > 0 and theta < 90:
  60. x = dis * round(math.cos(math.radians(theta)), 5)
  61. y = dis * round(math.sin(math.radians(theta)), 5)
  62. elif theta == 90:
  63. x = 0
  64. y = dis
  65. elif theta > 90 and theta < 180:
  66. x = dis * -round(math.cos(math.radians(180-theta)), 5)
  67. y = dis * round(math.sin(math.radians(theta)), 5)
  68. elif theta == 180:
  69. x = -dis
  70. y = 0
  71. #print('finish')
  72. elif theta >= 1000:
  73. x = 0
  74. y = 0
  75. theta -= 1000
  76. else:
  77. print('finish')
  78. break
  79.  
  80. data = [[x, y]]
  81. with open('radar_180.csv', 'a') as f:
  82. writer = csv.writer(f)
  83. writer.writerows(data)
  84. theta += 1
  85. angle += 1
  86. if angle > 90:
  87. servo_angle(-90)
  88. Servo.stop()
  89. GPIO.cleanup()
  90. sys.exit()
  91. else:
  92. #print(angle)
  93. time.sleep(1)
  94. except KeyboardInterrupt:
  95. servo_angle(-90)
  96. Servo.stop()
  97. GPIO.cleanup()
  98. sys.exit()

実行結果:グラフ化(レーダーの結果)

部屋の角(直角)にセンサーを置いて実行した結果です。

かなりなまったグラフになりました。

グラフ

なまった原因

  • 超音波が多く反射し減衰した
  • サーボモーターの回転角の制御が不安定

以上の2つが考えられます。

原因の多くはサーボモーターの回転角の制御が不安定だったことにあると考えられます。

サーボモーターはPWM制御を行うことで指定した角度まで回転しますが、今どこまで回っているのかをプログラム上で読み取ることは原理的に不可能です。

「1°回転させる」場合はPWMのパルス幅もかなり短くなっているはずです。

誤差が発生していてもおかしくないです。

回転子の位置を把握しながら制御するモーターが使用できれば回転角の誤差はなくなります。

Writer

アイコン
Python×Raspi IoTシステム・Bot・ラズパイの記録
  • プログラミング
  • IoT
  • Python
\FOLLOW ME/ 𝕏

Ranking

blogmura_pvcount

Community

Search