All sensors (Amphenol)製 差圧センサー DLHR-F50D-E1BD-C-NAV8 をRaspberry Pi + Pythonで動かしてみた記録です。
はじめに
差圧を計測してあれこれ検出するデバイスを作ってみたくて、買ってきたセンサーで値を取得するところまでの記録となります。
I2Cでアドレスを指定せずにデータを読み書きした経験がなく、「アドレスは指定するもの」という思い込みでread_byte
の存在を知らずステータスの読み出しではまりました。
環境
- Raspberry Pi 4 model B 4GB(Raspberry Pi OS arm64 Release:10 Codename:buster )
- Python: Python 3.9.2
センサーの選定
趣味の工作なので手に入るならOKな感じで選びます。
###要求
項目 | 説明 |
---|---|
高感度 | 数Paを検出したい |
測定周波数 | 40Hz |
ポート | チューブ接続が可能 |
電源電圧 | 3.3Vで動くとうれしい |
接続 | デジタル、I2Cだと楽 |
パッケージ | 使いやすければよし |
###候補
メーカー | 型式 | 仕様 |
---|---|---|
All sensors (Amphenol) | DLHR-F50D | ±125Pa |
Honeywell | HSCDRRN001ND2A3 | ±125Pa |
Sensirion | SDP810-125PA | ±125Pa |
Omron | D6F-PH0505 | ±50Pa |
お財布と相談しながら実験のしやすさ等考慮した結果
を選定。
###主な仕様
項目 | 仕様 |
---|---|
メーカー | All sensors (Amphenol) |
型式 | DLHR-F50D-E1BD-C-NAV8 |
レンジ | ±125Pa(±0.5inH2O) |
デジタル分解能 | 17.7bit |
データ更新時間 | 4.1ms max(Single測定) , 61.9ms max(16回平均化) |
耐圧 | 25kPa |
電圧 | 1.75 to 3.60 Vdc |
接続 | I2C / SPI |
パッケージ | DIP8ピン |
コード
仕様書を読んで書きます
from smbus2 import SMBus
import time
ADDRESS = 0x29
OS_DIG = 0.5 * 2 ** 24 # For Differential Operating Range sensors
FSS_IN_H2O = 2 * 0.5 # For Differential Operating Range sensors : 2 x Full Scale Pressure.
PA_CONVERSION = 249.089 # Conversion factor from "inH2O" to "Pa"
FSS_PA = FSS_IN_H2O * PA_CONVERSION # FSS/2 for "Pa"
START_SINGLE = 0xAA
START_AVERAGE2 = 0xAC
START_AVERAGE4 = 0xAD
START_AVERAGE8 = 0xAE
START_AVERAGE16 = 0xAF
class DifferentialPressureSensorDLHR_F50D():
'''
### Class for using All sensoers (Amphenol) DLHR-F50D-E1BD-C-NAV8 with Raspberry Pi
For simple use, you can instantiate it and then call the method "read_p()" to get the differential pressure in the return value.
'''
def __init__(self) -> None:
self.address = ADDRESS
self.status = 0x00
self.power = False
self.busy = False
self.mode = 0x00
self.memory_error = False
self.alu_error = False
self.rawp = 0
self.rawt = 0
self.pressure = 0
self.temprature = 0
self.bus = SMBus(1)
pass
def send_start(self) -> None:
'''Send Measurement Commands to the sensor when called'''
self.bus.write_byte(self.address, START_AVERAGE4)
def status_read(self) -> None:
self.status = self.bus.read_byte(self.address)
if self.status & 0x40 == 0x40:
self.power = True
else:
self.power = False
if self.status & 0x20 == 0x20:
self.busy = True
else:
self.busy = False
self.mode = (self.status & 0x18) >> 3
if self.status & 0x04 == 0x04:
self.memory_error = True
else:
self.memory_error = False
if self.status & 0x01 == 0x01:
self.alu_error = True
else:
self.alu_error = False
def read_busy(self) -> bool:
'''
### After reading the status register, it retrieves only the Busy status from the result and returns the result.
Return:
Sensor busy status (bool)
'''
self.status = self.bus.read_byte(self.address)
if self.status & 0x20 == 0x20:
self.busy = True
else:
self.busy = False
return self.busy
def poll_busy(self):
'''Keep reading the status until Busy becomes False.'''
while self.read_busy():
pass
def correction_p(self) -> None:
'''Returns the raw pressure value as a translated pressure value.'''
self.pressure = 1.25 * ((self.rawp - OS_DIG)/ 2**24) * FSS_PA
def correction_t(self) -> None:
'''Returns the raw temperature value as a translated temperature value.'''
self.temprature = (self.rawt * 125)/ 2**24 - 40
def read(self):
'''Get the raw value from the sensor.'''
data = self.bus.read_i2c_block_data(self.address, 0x00, 7)
self.rawp = (data[1] << 16) | (data[2] << 8) | data[3]
self.rawt = (data[4] << 16) | (data[5] << 8) | data[6]
def read_p(self) -> float:
'''
### Get the corrected differential pressure
Return:
Corrected differential pressure
'''
self.send_start()
self.poll_busy()
self.read()
self.correction_p()
return self.pressure
def read_t(self) -> float:
'''
### Get the corrected temprature
Return:
Corrected temprature
'''
self.send_start()
self.poll_busy()
self.read()
self.correction_t()
return self.temprature
def main(): # Sample usage
ZERO_OFFSET = 2 # Zero point correction
dlhr_f50d = DifferentialPressureSensorDLHR_F50D()
while True:
p = dlhr_f50d.read_p()
print("pressure :" + str(round(p - ZERO_OFFSET ,4)))
time.sleep(1)
if __name__ == '__main__':
main()
##はまりポイント
このセンサーはEOC
ピンの状態を監視することで、測定開始コマンド書き込み後にセンサーが読み取りReadyになったのかを知ることができますが、EOC
ピンを使用しない場合は、Status Read
コマンドを送信し続けてBusy
をポーリングすることができます。
仕様書によると、ステータスを読み出すには
When requesting sensor status over I2C, the host simply performs a 1-byte read transfer.
Read Sensor Status | i2C | Read of 1 byte from device.
とだけ書いてあります。
これをよく考えずにRead Sensor Data
では最初の1バイトで返ってくるみたいだし、
0x00
を読めばよいと勝手に解釈して、
from smbus2 import SMBus
bus = SMBus(1)
status = bus.read_byte_data(0x29, 0x00)
print(status)
こう書くと電源のステータスと合わせてstatus = 0b01100000 = 0x60 = 96
が返ってきて永遠にBusyのままです。
まだMeasurement Command
を送ってないのにBusyが立ってるのはおかしいので、「壊しちゃったかな?」とか考えながら、とりあえずMeasurement Command
してからしばらくしてRead Sensor Data
するとちゃんと値が返ってきます。
Busyが正しく読めないとtime.sleep()などで待ってから読み出すしかありません。
困ったなぁと思いながらしばらく経ってsmbus2のページをよく読んでみると、1バイト読み出す関数は2つあって、
レジスタアドレスを指定して読み出すread_byte_data
と
read_byte_data(i2c_addr, register, force=None)
単に1バイトを読み出すread_byte
がありました。
read_byte(i2c_addr, force=None)
Read of 1 byte from device.
と指示され、読み出すアドレスの指定がないわけですから、read_byte_data
ではなく read_byte
を使わないといけなかったわけです。
Measurement Command
はwrite_byte_data
じゃなくてちゃんとwrite_byte
使って書いてたのになぜ気が付かなかったのか...