5ドル!ラズパイ・ゼロ(Raspberry pi Zero)でIoT (30) 距離センサ1 I2C VL6180X

0~100 mmの範囲で、1mmの分解能

 VL6180Xは、ST Microelectronicsの赤外線近接距離センサ・モジュールで、センサに反射する時間から距離を計算します。1mmの分解能では20cm、2mmの分解能では40cm、3mmの分解能では60cmまでの距離を測れます。

 スイッチサイエンスで VL6180X 赤外線近接距離センサモジュール を入手しました。モジュールでは、2.8Vで動作するVL6180X用の電圧レギュレータと、プルアップ抵抗が実装されています。

VL6180Xモジュールのスペック

  • 動作電圧 2.7~5.5V
  • 測定範囲 0~100mm
  • 環境光 1~100000lux
  • インターフェース I2C(最大400kHz)
  • スレーブ・アドレス 0x29 (7ビット)、変更可

プルアップ抵抗は削除がのぞましいが

 ラズパイ本体にI2Cの信号にプルアップ抵抗が取り付けられているので、モジュール上のR1、R2のプルアップ抵抗を取り去ります。実際にプルアップ抵抗をそのままにしてつないだところ通信ができたので、そのままにしました。MOS FETによるレベル・コンバータが入っているのが理由かもしれません。

接続

 二つのGPIOピンは、割り込み、複数VL6180Xを利用する際に使われるようです。

VL6180X ラズパイ
SCL 5番ピン
GND 9番ピン
SDA 3番ピン
Vin 1番ピン 3.3V

 i2cdetect -y 1 で接続を確認しました。スレーブ・アドレスは0x29です。内部のレジスタによって変更することができます。外部に設定用ピンは出ていません。

プログラム

 このセンサはレジスタ・アドレスが16ビットです。smbusライブラリではアドレスは8ビットを前提にしています。write_i2c_block_dataでは、スレーブ・デバイスにある内部レジスタ・アドレスの指定は8ビットで行いますが、そのあとにbyte単位のデータ・リストを送れます(最大32バイト)。アドレスの1バイトとリスト中の1バイトは連続して送られるので、タイミング上区別はされていません。

 したがって、16ビットのアドレスの上位byteをレジスタ・アドレスのデータとし、下位はリストの最初のbyteにして送ります。

  • write_block_data(スレーブ・アドレス,アドレスの上位バイト,[アドレスの下位バイト,書き込むデータ,...])

 とりあえず、データが取得できればというレベルのプログラムです。内部でデータが用意できたというフラグ(0x004f)があり、ポーリングで読み取ることでデータ取得時間を短縮できます。

#!/usr/bin/python
import time
import smbus
addr=0x29
bus = smbus.SMBus(1)

VL6180X_SYSTEM_FRESH_OUT_OF_RESET = 0x0016
VL6180X_SYSRANGE_MAX_CONVERGENCE_TIME = 0x001C
VL6180X_SYSRANGE_RANGE_CHECK_ENABLES = 0x002D
VL6180X_SYSRANGE_EARLY_CONVERGENCE_ESTIMATE = 0x0022
VL6180X_SYSALS_INTEGRATION_PERIOD = 0x0040
VL6180X_SYSALS_ANALOGUE_GAIN = 0x3F
VL6180X_FIRMWARE_RESULT_SCALER = 0x0120
VL6180X_SYSRANGE_START = 0x0018
VL6180X_RESULT_RANGE_VAL = 0x0062
VL6180X_SYSTEM_INTERRUPT_CLEAR = 0x0015
VL6180X_SYSALS_START = 0x0038
VL6180X_RESULT_ALS_VAL = 0x0050

def read(register16):
a1 = (register16 >> 8) & 0xFF
a0 = register16 & 0xFF
bus.write_i2c_block_data(addr, a1, [a0])
return bus.read_byte(addr)

def read16(register16):
a1 = (register16 >> 8) & 0xFF
a0 = register16 & 0xFF
bus.write_i2c_block_data(addr, a1, [a0])
data16h = bus.read_byte(addr)
data16l = bus.read_byte(addr)
return (data16h << 8) | (data16l & 0xFF)

def WriteByte(register16, data):
a1 = (register16 >> 8) & 0xFF
a0 = register16 & 0xFF
bus.write_i2c_block_data(addr, a1, [a0, (data & 0xFF)])

def WriteByte16(register16, data16):
a1 = (register16 >> 8) & 0xFF
a0 = register16 & 0xFF
d1 = (data16 >> 8) & 0xFF
d0 = data16 & 0xFF
bus.write_i2c_block_data(addr, a1, [a0, d1, d0])

#init
if read(VL6180X_SYSTEM_FRESH_OUT_OF_RESET) == 1:
print "sensor is ready."
WriteByte(0x0207, 0x01)
WriteByte(0x0208, 0x01)
WriteByte(0x0096, 0x00)
WriteByte(0x0097, 0xfd)
WriteByte(0x00e3, 0x00)
WriteByte(0x00e4, 0x04)
WriteByte(0x00e5, 0x02)
WriteByte(0x00e6, 0x01)
WriteByte(0x00e7, 0x03)
WriteByte(0x00f5, 0x02)
WriteByte(0x00d9, 0x05)
WriteByte(0x00db, 0xce)

WriteByte(0x00dc, 0x03)
WriteByte(0x00dd, 0xf8)
WriteByte(0x009f, 0x00)
WriteByte(0x00a3, 0x3c)
WriteByte(0x00b7, 0x00)
WriteByte(0x00bb, 0x3c)
WriteByte(0x00b2, 0x09)
WriteByte(0x00ca, 0x09)
WriteByte(0x0198, 0x01)
WriteByte(0x01b0, 0x17)
WriteByte(0x01ad, 0x00)
WriteByte(0x00ff, 0x05)
WriteByte(0x0100, 0x05)
WriteByte(0x0199, 0x05)
WriteByte(0x01a6, 0x1b)
WriteByte(0x01ac, 0x3e)
WriteByte(0x01a7, 0x1f)
WriteByte(0x0030, 0x00)
#default_settings
# Recommended : Public registers - See data sheet for more detail
WriteByte(0x0011, 0x10); # Enables polling for 'New Sample ready' when measurement completes
WriteByte(0x010a, 0x30); # Set the averaging sample period (compromise between lower noise and increased execution time)
WriteByte(0x003f, 0x46); # Sets the light and dark gain (upper nibble). Dark gain should not be changed.
WriteByte(0x0031, 0xFF); # sets the # of range measurements after which auto calibration of system is performed
WriteByte(0x0040, 0x63); # Set ALS integration time to 100ms DocID026571 Rev 1 25/27 AN4545 SR03 settings27
WriteByte(0x002e, 0x01); # perform a single temperature calibration of the ranging sensor

#Optional: Public registers - See data sheet for more detail
WriteByte(0x001b, 0x09); # Set default ranging inter-measurement period to 100ms
WriteByte(0x003e, 0x31); # Set default ALS inter-measurement period to 500ms
WriteByte(0x0014, 0x24); # Configures interrupt on 'New Sample Ready threshold event'
WriteByte(0x016, 0x00); #change fresh out of set status to 0

# Additional settings defaults from community
WriteByte(VL6180X_SYSRANGE_MAX_CONVERGENCE_TIME, 0x32)
WriteByte(VL6180X_SYSRANGE_RANGE_CHECK_ENABLES, 0x10 | 0x01)
WriteByte16(VL6180X_SYSRANGE_EARLY_CONVERGENCE_ESTIMATE, 0x7B)
WriteByte16(VL6180X_SYSALS_INTEGRATION_PERIOD, 0x64) #100ms
WriteByte(VL6180X_SYSALS_ANALOGUE_GAIN, 0x20) #x40
WriteByte(VL6180X_FIRMWARE_RESULT_SCALER, 0x01)

#main
#distance
WriteByte(VL6180X_SYSRANGE_START, 0x01) #0x03 renzoku
time.sleep(0.1)
distance = read(VL6180X_RESULT_RANGE_VAL)
WriteByte(VL6180X_SYSTEM_INTERRUPT_CLEAR, 0x07)
print distance,"mm"

#ambient_light
WriteByte(VL6180X_SYSALS_START, 0x01)
time.sleep(0.5)
light = read16(VL6180X_RESULT_ALS_VAL)
WriteByte(VL6180X_SYSTEM_INTERRUPT_CLEAR, 0x07)
#print read(VL6180X_SYSALS_ANALOGUE_GAIN)
#print read16(VL6180X_SYSALS_INTEGRATION_PERIOD)
print light*0.32*100/(32*100),"lux"
#Copyright (c) 2014-2015 Arnie Weber. All rights reserved.

距離の計算

 データシートのサンプル通りに設定しています。

 計測間隔(Inter-measurement period)を100ms(0x09)に設定します。

 ワンショット計測モードで計測をスタートし、0.1秒待ち、データを読みます。読んだ値はmm単位なので、何も変換しません。

環境光の計算

 ゲインは32(0x20)、積分時間は100ms(0x64)を設定して、下記の変換式でluxを得ます。

 読み出した測定値 ×ALS校正値 ×100 / (ALSゲイン*ALS積分時間ms)

 ALS校正値はセンサの上にフィルタを置かないときに0.32です。しかし、桁数はあっていますが、照度計と比べると50%ほど低い値になっています。どこかデータシートを正確に読み取って反映できていないところがあるかもしれません。

参考文献

 プログラムの中で、  #default_settingsと#Optional:のパラメータは、Application noteのCのプログラムをpythonに変換しました。最後に# Additional settings defaults from communityで一部の値を変更しています。これは、https://bitbucket.org/310weber/st_vl6180xのプログラムに書かれていた変更項目です。

回路図 https://www.pololu.com/file/0J960/vl6180x-time-of-flight-distance-sensor-carrier-schematic.pdf


Application note AN4545 https://www.pololu.com/file/download/AN4545.pdf?file_id=0J962

Design tip DT0037 https://www.pololu.com/file/download/DT0037.pdf?file_id=0J963

データシート https://www.pololu.com/file/0J961/VL6180X.pdf

環境の明るさで距離は変わる?

 明るさを変えて、距離が変化するかを確かめました。白色のアルミ・ブロックとの距離を測りました。

  ほぼ真っ暗 机の上の明るさ LEDライトを照射
明るさ[lux] 0.33 12.9 73.6
距離[mm] 35 35 37

 センサにどのように光が入るかで誤差が出るかもしれませんが、わずかです。このあたりは、反射するものの材質なども影響するかもしれません。

 

※プログラムを仮にvl6180.pyと/home/piに保存すると、sudo chmod 755 vl6180.py で実行権を付け、ターミナルから、python vl6180.pyで実行します。I2CやSPIのグループにpiユーザが属しているので、sudoは不要です。
 プログラム・リストは、表示の関係でTabキーが無視されるので、スペースに代えてあります。また、リスト中を2回クリックすると全選択になるので、CTRL-Cでコピーし、テキスト・エディタにCTRL-Vで貼り付けて利用してください。ラズパイに持っていくと、リターン・コードなどが化けていることがあるので、一度消して、ラズパイのテキスト・エディタで改行してください。

※I2Cの有効化は、この説明を参照ください。1-Wireと同じく、I2CやSPIもEnableにチェックを入れています。