RaspberryPi 3 Model B+と赤外線距離センサー(GP2Y0A02YK)で距離を測定する
Contents
赤外線距離センサー
RaspberryPi 3 Model B+にシャープの赤外線距離センサー(GP2Y0A02YK)を接続して距離を測定するプログラムを作成した時の備忘録。
尚、この赤外線距離センサーはアナログ信号を返すのでラズパイで直接処理をする事ができないので、アナログ信号をデジタル信号に変換するADコンバーター(MPC3208)を使っている。
記事後半でADコンバーターからの電圧を取得するまでのロジックについて説明している。
また同様の内容の動画を記事の最後にリンクを張っている。
ADコンバーター等の説明は動画の方が分かりやすいかも知れないので確認してみて欲しい。
今回の記事で使用した赤外線距離センサーのスペックは以下の通り
型番、外観 | GP2Y0A02YK |
測定可能距離 | 20cm~150cm |
出力 | アナログ出力(電圧) 0V~3Vの範囲で測定距離を電圧で出力する(詳しくは後述) RaspberryPiで扱うにはADコンバーターが必要なのでMCP3208を用意した |
平均消費電流 | 33mA |
動作電圧 Operating Supply voltage | 4.5~5.5V RaspberryPiの2番ピン(5V)に接続した |
上記の他に10cm~80cmの測定が可能なGP2Y0A21も一緒に購入したのでそのうちに試してみようと思っている。
また今回は物体との距離を測定するのみたがAWS IoT Coreに接続してリアルタイムの測定結果をクラウドにパブリッシュ(アップロード)することもできる。
IoT Coreとの接続はこちらの記事を参考にしてほしい。
接続方法
GP2Y0A02YKの出力はアナログなのでADコンバーター(MCP3208)経由でRaspberryPiに接続する。
またGP2Y0A02YKに標準で付属しているリード線は何故かVcc(5V)が黒、GNDが茶色、Vo(output)は白なので注意が必要。
SPIの有効化
事前にSPIを有効にする。
RaspberryPiのメニューから設定ー>Raspberry Piの設定。
インターフェースタブでSPIを有効にして「OK」で保存後にRaspberryPiを再起動する。
spidevのインストール
以下のコマンドでspidevをインストールした。
sudo apt-get install python-spidev
プログラム
プログラムは以下の通り(解説は後述)
# -*- coding: utf-8 -*-
import time # time(sleepを使うためのモジュール)のインポート
import spidev # spidev(spi通信を行うためのモジュール)のインポート
import numpy as np # 配列
CHN = 0 # MCP3208のCH(0~7)
VOLTS = 5 # 電圧(3.3v or 5v)
class ADConverterClass: # AD Converter(MCP3208)から値を取得するクラス
def __init__(self, ref_volts, ch): # コンストラクタ
self.ref_volts = ref_volts # 電圧セット
self.spi = spidev.SpiDev() # SpiDevのインスタンスを作成
self.spi.open(0,0) # 0:SPI0、0:CE0
self.spi.max_speed_hz = 1000000 # 1MHz SPIのバージョンアップによりこの指定をしないと動かない!
self.ch = ch # GP2Y0A02のMCP3208への入力チャンネル
def get_voltage(self, ch): # A/Dコンバータ(MCP3208)で電圧を取得する
raw = self.spi.xfer2([((0b1000+ch)>>2)+0b100,((0b1000+ch)&0b0011)<<6,0]) # Din(RasPi→MCP3208)
raw2 = ((raw[1]&0b1111) << 8) + raw[2] # Dout(MCP3208→RasPi)
volts = (raw2 * self.ref_volts ) / float(4095) # 取得した値を電圧に変換する(12bitなので4095で割る)
volts = round(volts,4) # 電圧を4桁に四捨五入する
return volts # 電圧を返す
def get_dist(self): # GP2Y0A02で距離を測定する
v_rate = np.array([[2.55,2.75,0.18,20], # ボルトと距離との関係の表
[2,2.55,0.18,30], # From Volts、To Volts、変換率、距離
[1.55,2,0.22,40],
[1.25,1.55,0.33,50],
[1.05,1.25,0.5,60],
[0.9,1.05,0.67,70],
[0.81,0.9,1.11,80],
[0.72,0.81,1.11,90],
[0.64,0.72,1.25,100],
[0.59,0.64,2,110],
[0.54,0.59,2,120],
[0.5,0.54,2.5,130],
[0.46,0.5,2.5,140],
[0.43,0.46,3.33,150],
[0,0.43,100,4450]])
volts = self.get_voltage(self.ch) # MCP3208のGP2Y0A02を入力したチャンネルの電圧を取得する
if volts > v_rate[0,1]: # 指定したVoltの範囲外の時
dist = 0
elif volts < v_rate[len(v_rate)-1,1]:
dist = 999
else:
for i in range(len(v_rate)):
if v_rate[i,0] <= volts and volts < v_rate[i,1]: # FromとToの範囲内のVoltを検索する
bs_volt = v_rate[i,0]
cm_rate = v_rate[i,2]
cm_base = v_rate[i,3]
break
dist = cm_base -((volts - bs_volt)*cm_rate*100) # 距離を計算する
return dist # 距離を返す
def Cleanup(self): # 終了処理
self.spi.close() # SpiDevの終了処理
if __name__ == '__main__': # main
ad_conv = ADConverterClass(ref_volts=VOLTS, ch=CHN) # Classのインスタンス作成
try:
while True:
__volts = ad_conv.get_voltage(ch=CHN) # ボルトを取得して表示
print("volts: {:8.2f}".format(__volts))
__dist = ad_conv.get_dist() # GP2Y0A02から推定した距離[cm]を取得する
print("GP2Y0A02の距離[cm]: {:8.2f}".format(__dist)) #コンソールに表示
time.sleep(1) # スリープ
except KeyboardInterrupt: # Ctl+C]
print("\nCtl+C")
except Exception as e:
print(str(e)) # 例外処理の内容をコンソールに表示
finally:
ad_conv.Cleanup() # ADConverterClassの終了処理
print("\nexit program") # プログラム終了
距離の計算方法について
GP2Y0A02YKは赤外線で測定した距離を電圧で出力するので電圧から距離を算出する必要がある。
GP2Y0A02YKのデータシートの3ページ目に電圧と距離の関係を表すグラフが載っていた。
縦軸が電圧で横軸が距離(cm)で距離が遠くなるにつれて電圧が低くなっているのだが線形(直線)ではないので単純に電圧の何らかの係数を掛けて算出できるわけではない。※1次関数で表すことができない。
近似曲線を算出しようとしたのだがExcelだと簡単に近似曲線が算出できるようなのだが残念ながらExcelは持っていない。
そのうちに誰かに借りて試してみようとは思っているがまずは別の方法で出来ないかを検討してみる。
よく見ると10cm単位では(ほぼ)直線なので電圧によって傾きを変えれば近似値を1次関数で計算できそうなので、その方法でいくことにした。
上記の例だと2.55v~2vの時は20cm~30cmの間で直線(1次関数)で近似できる。
以下、2v~1.55vの時は30cm~40cmの間で・・・と続く。
原始的だけど、グラフをA4用紙に印刷してさしがねで値を測定した。
v_rate = np.array([[2.55,2.75,0.18,20], # ボルトと距離との関係の表
[2,2.55,0.18,30], # From Volts、To Volts、変換率、距離
[1.55,2,0.22,40],
[1.25,1.55,0.33,50],
[1.05,1.25,0.5,60],
[0.9,1.05,0.67,70],
[0.81,0.9,1.11,80],
[0.72,0.81,1.11,90],
[0.64,0.72,1.25,100],
[0.59,0.64,2,110],
[0.54,0.59,2,120],
[0.5,0.54,2.5,130],
[0.46,0.5,2.5,140],
[0.43,0.46,3.33,150],
[0,0.43,100,4450]])
配列の値は左から
- From電圧
- To電圧
- 変換率(傾き)
- 元となる距離
の4つの要素からなっている。
ADコンバータから取得した電圧からFrom、Toの範囲内の行を検索して、マッチした行の3.変換率(傾き)と4.元となる距離を取得する。
変換率(傾き)は20cm~30cm(差は10cm)の距離で電圧が2.55V~2V(差は0.55V)なので10÷0.55÷100=0.18(小数点以下3桁目を四捨五入)で設定している。
そして50行目の計算式で距離を算出する。
dist = cm_base -((volts - bs_volt)*cm_rate*100) # 距離を計算する
例えばADコンバータからの電圧が1.3Vだった時は上から1.25V~1.55Vの範囲内なので4行目の
[1.25,1.55,0.33,50]
がヒットして、
- From電圧:1.25 v
- To電圧:1.55 v
- 変換率:0.33 cm/v
- 元となる距離:50 cm
となる。
1.3vー1.25v=0.05v
0.05v×0.33cm/v(変換率)×100=1.65cm
50cm(元となる距離)ー1.65cm=48.35cm(測定したい距離)
と計算した。
MCP3208
ADコンバータ(MCP3208)から電圧を取得するロジックについての備忘録。
raw = self.spi.xfer2([((0b1000+ch)>>2)+0b100,((0b1000+ch)&0b0011)<<6,0]) # Din(RasPi→MCP3208)
raw2 = ((raw[1]&0b1111) << 8) + raw[2] # Dout(MCP3208→RasPi)
volts = (raw2 * self.ref_volts ) / float(4095) # 取得した値を電圧に変換する(12bitなので4095で割る)
1行目はRaspberryPiからMCP3208にチャンネルを指定する信号を送っている。
2行目はMCP3208からRaspberryPiで電圧を12bitで受け取っている。
3行目は2行目で受けた値を電圧に変換している
1行目の解説
まずはMCP3208のデータシートを参照。
14ページの表5-2がMCP3208の構成ビット。
データシートより
上記の表5-2よりCH0の時は0b1000、CH1は0b1001・・・と指定する事が分かる。
次に16ページの図6-1(spiモード0,0)のDinを見ると8ビットづつ3セットでチャンネルを指定することが分かる。
データシートより
仮にCH1にGP2Y0A02YKを繋いだ場合、
0b00000110, 0b01000000, 0b00000000
とビットを指定したい(図6-1のDinを参照)
(0b1000+ch)
で表5-2のSINGLE/DIFF D2 D1 D0のビットを生成して>>2で後半の2ビット(D1、D0)を削除、更に+0b100で1セット目の8ビット(0b00000110)を作成している。
次に(0b1000+ch)までは先程と同様。
(0b1000+ch)&0b0011
で後半2ビットの論理積(AND演算)で前半の2ビット(SINGLE/DIFF、D2)を0にして、<<6で左に6ビットシフトして2セット目の8ビット(0b01000000)を作成している。
3セット目の8ビットは無視されるビットなのでとりあえず0をセットした。
2行目の解説
Doutはraw[0]、raw[1]、raw[2]に8ビットづつセットされるのでraw[0]は不要、raw[1]の後半4ビットとraw[2]の8ビットだけを使用する。
raw[1]の後半4ビットとraw[2]の8ビットをつなげて合計12ビットを作りたい。
raw[1]は
raw[1]&0b1111
で後半4ビットの論理積(前半4ビットを無視)後、<< 8で左に8ビットシフトしてraw[2]を加えて合計12ビットの値を得る。
3行目の解説
12ビット=2の12乗=4,096=0~4,095の値を取る。
5V(ref_volts)が4,095になるので4,095で割ることにより電圧を求める。
その他
その他にハマったのは13行目の
self.spi.max_speed_hz = 1000000 # 1MHz SPIのバージョンアップによりこの指定をしないと動かない!
この行が無いと正しい電圧が取得されなかった。
インターネット上の過去のプログラムではこの行が無いプログラムが多いのだがどうやらspidevがバージョンアップして仕様が変わった模様なので要注意。
実際の計測値との比較
RaspberryPiの箱を置いて実際に計測をしてみた。
計測を3回行い平均と比較したときの結果は以下の通り。
実際の距離 | 測定結果1 | 測定結果2 | 測定結果3 | 測定結果平均 | 誤差 |
20cm | 20.15 | 19.94 | 20.26 | 20.12 | +0.60% |
30cm | 29.34 | 29.52 | 29.71 | 29.52 | -1.60% |
40cm | 39.88 | 39.96 | 39.82 | 39.98 | -0.27% |
50cm | 49.75 | 50.59 | 49.55 | 49.96 | -0.08% |
60cm | 61.26 | 59.33 | 60.93 | 60.51 | +0.85% |
70cm | 71.79 | 71.75 | 71.02 | 71.44 | +2.06% |
80cm | 81.29 | 83.18 | 81.01 | 81.83 | +2.29% |
90cm | 91.79 | 91.47 | 93.92 | 92.39 | +2.66% |
100cm | 99.56 | 98.35 | 99.41 | 99.11 | -0.89% |
110cm | 110.06 | 109.8 | 110.06 | 109.97 | -0.03% |
120cm | 121.6 | 118.1 | 117.14 | 118.95 | -0.87% |
130cm | 127.1 | 126.5 | 126.8 | 126.8 | -2.46% |
140cm | 143.57 | 136.88 | 138.4 | 139.62 | -0.27% |
150cm | 149.27 | 147.64 | 148.83 | 148.58 | -0.95% |
およそ3%以内の誤差の範囲に収まっているが、3回の計測の中でも多少のばらつきがある事が分かる。
また対象物との距離が長くなるとばらつきが増すようにも感じた。
対象物をRaspberryPiの箱から別のモノに変えた所、微妙に測定結果が変わったので対象物の材質や色によっても違いが出てくるのだと思う。
またはGP2Y0A02YKの個体差も多少はあるのかも知れない。
常に同じ材質のモノとの距離を測定するのであれば前述の配列表の値を変えてやれば、より誤差の少ない測定が可能になると思われる。
動画について
この記事の内容をYoutubeで解説している。
ADコンバーターの説明は動画の方が理解しやすいと思うので上記の説明で良く分からない人は見て欲しい。
最近のコメント