よく調べようという教訓が得られます。
2021/10/12:コード修正(2個目以降のファイルの値が更新されていなかった)
初めに
Raspberry Piで気圧ロガーを作るためにセンサーモジュールを買ったが、販売元のページにはArduino用のCライブラリしか無かったので、仕方なくPythonのドライバを書いたら、実はチップメーカーのGithubにすでにPythonのコードが公開されていた。というオチの記録です。
目標
- 気圧センサを使用したデータロガーを作る。
環境
- Raspberry Pi 4 model B 4GB(Raspberry Pi OS arm64 Release:10 Codename:buster )
- この記事で作成したDockerコンテナ
- Python 3.9.2
- RPi.GPIO, i2c-tools, smbus :などが利用可能
- grove.py :Grove Base Hat for Raspberry PiとGroveシステムのセンサ類のライブラリ
- Grove Base Hat for Raspberry Pi
- GPIO,I2C,ADC,URAT,PWMを手軽に使えるようにするコネクターがいっぱい生えたHAT、便利
- Grove - High Precision Barometric Pressure Sensor DPS310
- 高精度な気圧センサモジュール
- infineon社のDPS310を搭載
- I2C接続で使用
リソース
販売店のページには以下のリンクがある。
- 製造元Wiki:Seeed Technology社解説Wikiページへのリンク
- Githubライブラリ:搭載センサーのメーカーinfineonのDPS310ライブラリリポジトリ(Arduino用)
- データシート:搭載センサーのメーカーinfineonのDPS310データシートへのリンク(リンク先はSeeed社)
- 回路図:モジュールの回路図やCADデータ
製造元WikiにはArduino用のリソースはあるがRaspberry Pi用はToDoとなっており記載がない
製造元Wikiに記載があるSeeed社のリポジトリもArduino用でした。
ここまで見て仕方ないので自分で書くしかないかなと思いセンサーを注文。
Pythonコードの作成
データシートとにらめっこしながらPythonでドライバを書きます。
ついでにCSVファイルを保存してデータロガーにしてしまいます。
infineon社ライブラリのCのコードを多分に参考してます。参考:ライセンス
import time
from grove.i2c import Bus
import multi_timer
import csv
import datetime
READ_INTERVAL = 1/128 # [sec]
FILE_SAVE_INTERVAL = 3600 # [sec]
SAVE_DIR = 'press_log/'
# i2c address setting
ADDRESS = 0x77
# Pressure Configuration (PRS_CFG)
PRESS_CONF = 0x71
# Pressure measurement rate: 0111 XXXX- 128 measurements pr. sec.
# Pressure oversampling rate: XXXX 0001 - 2 times (Low Power). Precision : 1 PaRMS.
# 0111 0001 = 0x71 highest measurements rate
'''DPS310 data sheet P.29 - 30
Pressure measurement rate: **) |Pressure oversampling rate:
0000 - 1 measurements pr. sec. |0000 - Single. (Low Precision)
0001 - 2 measurements pr. sec. |0001 - 2 times (Low Power).
0010 - 4 measurements pr. sec. |0010 - 4 times.
0011 - 8 measurements pr. sec. |0011 - 8 times.
0100 - 16 measurements pr. sec. |0100 *)- 16 times (Standard).
0101 - 32 measurements pr. sec. |0101 *) - 32 times.
0110 - 64 measurements pr. sec. |0110 *) - 64 times (High Precision).
0111 - 128 measurements pr. sec. |0111 *) - 128 times.
Applicable for measurements |1xxx - Reserved
in Background mode only |*) Note: Use in combination with a bit shift.
**) note: There is a limit to the oversampling rate setting from 8 measurements pr. sec.
(At 128 measurements per second, the oversampling rate is limited to 2)
'''
# Temperature Configuration(TMP_CFG)
TEMP_CONF = 0xF0
# Temperature measurement: 1XXX XXXX - External sensor (in pressure sensor MEMS element)
# Temperature measurement rate: X111 XXXX - 128 measurements pr. sec.
# Temperature oversampling (precision): 0000 - single. (Default) - Measurement time 3.6 ms.
# 1111 0000 = 0xF0 highest measurements rate
'''DPS310 data sheet P.31
Temperature measurement |
0 - Internal sensor (in ASIC) |
1 - External sensor (in pressure sensor MEMS element) |
Temperature measurement rate: |Temperature oversampling (precision):
000 - 1 measurement pr. sec. |0000 - single. (Default) - Measurement time 3.6 ms.
001 - 2 measurements pr. sec. |
010 - 4 measurements pr. sec. |
011 - 8 measurements pr. sec. |
100 - 16 measurements pr. sec. |
101 - 32 measurements pr. sec. |
110 - 64 measurements pr. sec. |
111 - 128 measurements pr. sec. |
Applicable for measurements in Background mode only |
'''
# Interrupt and FIFO configuration (CFG_REG)
INT_AND_FIFO_CONF = 0x00
# T_SHIFT: bit3: Temperature result bit-shift: 0 - no shift result right in data register.
# P_SHIFT: bit2: Pressure result bit-shift: 0 - no shift result right in data register.
# not use interrupts.: 0000 XXXX
# not use FIFO. : XXXX XX0X
# not use SPI. : XXXX XXX0
# 0000 1100 = 0x00
'''DPS310 data sheet P.33
If don't use interrupts. = 0000 XXXX
T_SHIFT | 3 | rw | Temperature result bit-shift
| 0 - no shift.
| 1 - shift result right in data register.
| Note: Must be set to '1' when the oversampling rate is >8 times.
P_SHIFT | 2 | rw | Pressure result bit-shift
| 0 - no shift.
| 1 - shift result right in data register.
| Note: Must be set to '1' when the oversampling rate is >8 times.
If don't use FIFO. = XXXX XX0X
If don't use SPI. = XXXX XXX0
'''
# Sensor Operating Mode and Status (MEAS_CFG)
OP_MODE = 0x07
# 111 - Background Mode Continous pressure and temperature measurement
# 0000 0111 = 0x07
'''DPS310 data sheet P.32
Bits 7 to 4 are read-only and various ready statuses.
Bit 3 is reserved.
MEAS_CTRL | 2:0 | rw | Set measurement mode and type:
| Standby Mode
| 000 - Idle / Stop background measurement
| Command Mode
| 001 - Pressure measurement
| 010 - Temperature measurement
| 011 - na.
| 100 - na.
| Background Mode
| 101 - Continous pressure measurement
| 110 - Continous temperature measurement
| 111 - Continous pressure and temperature measurement
'''
# Compensation Scale Factors
SCALE_FACTOR_KP = 1572864 # Oversampling Rate 2 times (Low Power)
SCALE_FACTOR_TP = 524288 # Oversampling Rate 1 (single)
'''DPS310 data sheet P.15
Compensation Scale Factors
Oversampling Rate | Scale Factor (kP or kT) | Result shift ( bit 2and 3 address0x09)
-------------------------------------------------------------------------------------------------
1 (single) | 524288 | 0
2 times (Low Power) | 1572864 | 0
4 times | 3670016 | 0
8 times | 7864320 | 0
16 times (Standard) | 253952 | enable pressure or temperature shift
32 times | 516096 | enable pressure or temperature shift
64 times (High Precision) | 1040384 | enable pressure or temperature shift
128 times | 2088960 | enable pressure or temperatureshift
'''
class pressure_sensor_DPS310():
def __init__(self):
time.sleep(0.1)
# I2C bus
self.bus = Bus(None)
# Measurement Settings
self.bus.write_byte_data(ADDRESS, 0x06, PRESS_CONF)
self.bus.write_byte_data(ADDRESS, 0x07, TEMP_CONF)
self.bus.write_byte_data(ADDRESS, 0x09, INT_AND_FIFO_CONF)
self.bus.write_byte_data(ADDRESS, 0x08, OP_MODE)
def __getTwosComplement(self, raw, length):
value = raw
if raw & (1 << (length - 1)):
value = raw - (1 << length)
return value
def read_calibration_coefficients(self):
# Read Calibration Coefficients
reg = {}
for i in range(0x10, 0x22):
reg[i] = self.bus.read_byte_data(ADDRESS,i)
Factors = {}
Factors['c0'] = self.__getTwosComplement(((reg[0x10]<<8 | reg[0x11])>>4), 12)
Factors['c1'] = self.__getTwosComplement(((reg[0x11] & 0x0F)<<8 | reg[0x12]), 12)
Factors['c00'] = self.__getTwosComplement((((reg[0x13]<<8 | reg[0x14])<<8 | reg[0x15])>>4), 20)
Factors['c10'] = self.__getTwosComplement((((reg[0x15] & 0x0F)<<8 | reg[0x16])<<8 | reg[0x17]), 20)
Factors['c01'] = self.__getTwosComplement((reg[0x18]<<8 | reg[0x19]), 16)
Factors['c11'] = self.__getTwosComplement((reg[0x1A]<<8 | reg[0x1B]), 16)
Factors['c20'] = self.__getTwosComplement((reg[0x1C]<<8 | reg[0x1D]), 16)
Factors['c21'] = self.__getTwosComplement((reg[0x1E]<<8 | reg[0x1F]), 16)
Factors['c30'] = self.__getTwosComplement((reg[0x20]<<8 | reg[0x21]), 16)
return Factors
def __calc_temp(self, raw_temp, Factors):
scaled_temp = raw_temp / SCALE_FACTOR_TP # Traw_sc = Traw/kT
compd_temp = Factors['c0'] * 0.5 + Factors['c1'] * scaled_temp # Tcomp (°C) = c0*0.5 + c1*Traw_sc
return scaled_temp, compd_temp
def read_temperature(self, Factors):
reg = {}
# read raw temperature
for i in range(0x03, 0x06):
reg[i] = self.bus.read_byte_data(ADDRESS,i)
raw_temp = self.__getTwosComplement(((reg[0x03]<<16) | (reg[0x04]<<8) | reg[0x05]), 24)
# calculate temperature
scaled_temp, compd_temp = self.__calc_temp(raw_temp, Factors)
return scaled_temp, compd_temp
def __calc_press(self, raw_press, scaled_temp, Factors):
# Praw_sc = Praw/kP
scaled_press = raw_press / SCALE_FACTOR_KP
# Pcomp(Pa) = c00 + Praw_sc*(c10 + Praw_sc *(c20+ Praw_sc *c30))
# + Traw_sc *c01 + Traw_sc *Praw_sc *(c11+Praw_sc*c21)
compd_press = Factors['c00'] + scaled_press * (Factors['c10'] + scaled_press\
* (Factors['c20'] + scaled_press * Factors['c30']))\
+ scaled_temp * Factors['c01'] + scaled_temp * scaled_press\
* (Factors['c11'] + scaled_press * Factors['c21'])
return compd_press
def read_pressure(self, scaled_temp, Factors):
reg = {}
for i in range(0x00, 0x03):
reg[i] = self.bus.read_byte_data(ADDRESS,i)
raw_press = self.__getTwosComplement(((reg[0x00]<<16) | (reg[0x01]<<8) | reg[0x02]), 24)
compd_press = self.__calc_press(raw_press,scaled_temp, Factors)
return compd_press
def main():
bus = Bus(None)
INTERVAL = READ_INTERVAL
timer = multi_timer.multi_timer(INTERVAL)
dps310 = pressure_sensor_DPS310() # Instance creation
Factors = dps310.read_calibration_coefficients() # Read Calibration Coefficients
while True:
today = datetime.date.today()
filename = str(SAVE_DIR + today.strftime('%Y%m%d') + '-' + time.strftime('%H%M%S') + '.csv')
try:
with open(filename, 'w', newline='') as f:
bus.write_byte_data(ADDRESS, 0x08, OP_MODE)
writer = csv.writer(f)
file_start_time = time.time()
while True:
timer.timer()
if timer.up_state == True:
timer.up_state = False
# process
scaled_temp ,temperature = dps310.read_temperature(Factors) # read and compensation temperature
press = dps310.read_pressure(scaled_temp, Factors) # read and compensation pressure
#print(str(time.time()) + "," + str(press))
data = [str(time.time()),str(press)]
writer.writerow(data)
if file_start_time+FILE_SAVE_INTERVAL <= time.time():
break
finally:
bus.write_byte_data(ADDRESS, 0x08, 0x00)
if __name__ == "__main__":
main()
上記コードは、時間分解能を上げたかったのでハイレートなセッティングです。
せっかくの精度を生かすなら4サンプル/secくらいで定数を
READ_INTERVAL = 1/4
# [sec]
PRESS_CONF = 0x2E
# 4spp 64 times oversampling
TEMP_CONF = 0xA0
# 4spp
INT_AND_FIFO_CONF = 0x04
# Pressure result bit-shift enable
SCALE_FACTOR_KP = 1040384
# 64 times (High Precision)
こんな感じに設定するといいと思います。
importしているタイマーは自作のクラスで単純なポーリングタイマーです。
import time
class multi_timer():
'''
Multiple timers can be used simultaneously in this class.
Create an instance with the interval [sec] as an argument.
'''
def __init__(self, interval):
self.last_time = 0.0
self.up_state = False
self.interval = interval
def timer(self):
'''
The timer method compares the current time with the interval
and updates the up_state at each call.
'''
if self.last_time + self.interval <= time.time():
self.last_time = time.time()
self.up_state = True
# Usage Example
def main():
INTERVAL_1s = float(1.0) # Enter the interval time in seconds
INTERVAL_10s = float(10.0) # Enter the interval time in seconds
# timer instance creation
timer_1s = multi_timer(INTERVAL_1s)
timer_10s = multi_timer(INTERVAL_10s)
while True:
timer_1s.timer() # Call the method to update the timer
if timer_1s.up_state == True:
timer_1s.up_state = False # required to manually rewrite 'up_state' to False
# Write the process here
print("1sec: " + str(time.time()))
timer_10s.timer()
if timer_10s.up_state ==True:
timer_10s.up_state = False # required to manually rewrite 'up_state' to False
# Write the process here
print("10sec: " + str(time.time()))
return
if __name__ == "__main__":
main()
実行するとカレント配下のpress_log/
にCSVファイルが出力されます。
1633533232.8374667,97542.54109208909
1633533232.8451686,97542.48074125986
1633533232.8530242,97543.69100189407
1633533232.8607929,97543.91064694956
1633533232.8689406,97543.04499677646
1633533232.8765638,97544.49214130614
1633533232.884714,97543.75997487147
1633533232.8925848,97543.13983414807
1633533232.9000964,97543.26033221664
1633533232.9077988,97542.63161833295
書いている途中(ほぼ完成)で見つけてしまったリポジトリ
あるじゃないですか(涙)
#おわりに
デバイスの仕様書を読みながらセンサを使えるようにするのは良いトレーニングになります。(涙)
動作はしましたが、精度の検証とかテストはしていないのでバグがあったらすみません。
参考文献
探すのを止めた時見つかることはよくある話で