MECHATRAXのブログ
弊社製品やサービスに関するブログです。企画や開発の際の参考になれば幸いです。
ラズパイ1個で ADPi Pro 2個を使う設定と複数チャンネルでの計測
ラズパイ1個で ADPi Pro 2個を使う設定と複数チャンネルでの計測
はじめに
タイトルの通り、高精度A/D変換モジュール ADPi Pro はラズベリーパイ1個につき最大2個まで使用可能です。
ADPi Pro は1個あたり4チャンネルの信号入力が可能です。2個使用することで、信号入力可能なチャンネル数を8チャンネルに増設できます。
これにより、1個の ADPi Pro では不足するチャンネル数を補うことができます。
今回は ADPi Pro 2個を設定し、SPIDEV を使用して複数チャンネルでのデータ計測およびサンプリング周期を測定してみます。
実行環境
ハードウェア
- Raspberry Pi 4 Model B x 1個
- ADPi Pro x 2個
- slee-Pi3 x 1個
slee-Pi3 は必須ではありませんが、ラズベリーパイへの安定した電源供給のために使用を推奨します。 - DCジャック変換ハーネス x 1本(slee-Pi3 同梱)
- ACアダプタ x 1個
- USB-シリアル変換ケーブル x 1本
ソフトウェア
- オペレーティングシステム
Raspberry Pi OS Lite (64bit) Bookworm - 弊社提供パッケージ
python3-adpi、adpi-utils-backend-spidev、adpi-utils - Python パッケージ
python3-pandas
注意事項
本記事では ADPi Pro と slee-Pi3 の基本的なセットアップ方法は紹介していません。
必要に応じて、次の参考資料をもとにセットアップを行ってください。
セットアップ用の参考資料
ADPi
・ 弊社 GitHub ページ
・ SPIを用いたラズベリーパイ用高精度A/D変換モジュール「ADPi Pro」のセットアップ
・ IIOサブシステムを用いたラズベリーパイ用高精度A/D変換モジュール「ADPi Pro」のセットアップ
slee-Pi3
・ 弊社 GitHub ページ
・ ラズベリーパイ用電源管理/死活監視モジュール「slee-Pi3」のセットアップ
設定
ADPi Pro を2個使用するための設定を行います。
ハードウェア
1個目は出荷時状態のままで、2個目の設定を変更します。
次の①~③の手順を行ってください。
①.DIPSW1 の DIP1と3 を ON に変更
②.JP2 を CS1(右側)に変更
③.JP3 を RD1(右側)に変更
※ 写真は変更済みの状態です。
ソフトウェア
次の1~3の手順を行ってください。
1./etc/adpi.conf を変更
次のコマンドで /etc/adpi.conf の [spi0.1] セクションに device=adpipro を追記します。
sudo sed -i '/\[spi0\.1\]/{n;s/device=/device=adpipro/}' /etc/adpi.conf
2.再起動
設定後にラズベリーパイを再起動してください。
3.設定の確認
設定が正しければ、次のようなコマンド結果となります。
mtx@raspberrypi:~$ ls /sys/bus/spi/devices/
spi0.0 spi0.1
※ IIO サブシステムを使用する場合は次のプルダウンをご参照ください。
IIO サブシステムの設定
1./etc/adpi.conf と /boot/firmware/config.txt を変更
次のコマンドで /etc/adpi.conf の [spi0.1] セクションに device=adpipro を追記します。
sudo sed -i '/\[spi0\.1\]/{n;s/device=/device=adpipro/}' /etc/adpi.conf
次のコマンドで /boot/firmware/config.txt に dtoverlay=adpipro-cs1 を追記します。
sudo sed -i '/dtoverlay=adpipro-cs0/i\dtoverlay=adpipro-cs1' /boot/firmware/config.txt
2.再起動
設定後にラズベリーパイを再起動してください。
3.設定の確認
mtx@raspberrypi:~$ ls /sys/bus/iio/devices/
iio:device0 iio:device1 trigger0 trigger1
サンプリング
複数チャンネルの計測およびサンプリング周期の測定を行います。
各サンプリング方法に応じた Python スクリプトを作成します。
注意事項
・ スクリプトをそのまま使用する際には、 事前に Python ライブラリの pandas をインストールしてください。
・ 電圧値は ADPi Pro の各チャンネルに抵抗を配置し、0V 付近が出力されるように調整しています。
1個1チャンネル
まずは ADPi Pro 1個の1チャンネルで計測します。
1ch_adpispi.py を作成して実行します。
1ch_adpispi.py 全体を見る
#!/usr/bin/python3
import spidev
import smbus
import adpi
import sys
from time import sleep
import time
import pandas as pd
RAW_OFFSET = (1 << 23)
RAW_SCALE = (
0.000596040,
0.000298020,
0.000149010,
0.000074500,
0.000037250,
0.000018620,
0.000009310,
0.000004650,
)
TEMP_VREF = 1.17
def v2k(rate, val):
for k, v in rate.items():
if v == val:
return k
def single_conversion(dev, ch):
c = dev.adc.channel[ch]
g, _ = dev.read_configuration()
dev.write_configuration(g, c)
_, r = dev.read_mode()
dev.write_mode(dev.adc.mode['single'], r)
while True:
if not dev.read_status() & 0x80:
break
raw = dev.read_data()
return raw, g
def set_calib(dev):
g = dev.adc.gain['1']
r = dev.adc.rate['470']
bias = dev.load_bias(g)
scale = dev.load_scale(g)
_, r = dev.read_mode()
for i in range(dev.channels):
dev.write_configuration(g, i)
dev.write_mode(dev.adc.mode['idle'], r)
dev.write_offset(bias[i])
dev.write_mode(dev.adc.mode['idle'], r)
dev.write_fullscale(scale[i])
def get_voltage(dev, ch):
raw, g = single_conversion(dev, ch)
return RAW_SCALE[g] * (raw - RAW_OFFSET)
if __name__ == "__main__":
spibus = 0
spics = 0
eeprombus = 1
eepromaddr = 0x57
gpiobus = 1
gpioaddr = 0x27
spi = spidev.SpiDev()
i2c = smbus.SMBus(eeprombus)
try:
spi.open(spibus, spics)
spi.mode = 0b11
spi.max_speed_hz = 1000000
ad = adpi.ADPiPro(spi, i2c, eepromaddr, gpioaddr)
set_calib(ad)
ch = '1'
runs = 100000
voltages = [0] * runs
timestamps = [0] * runs
for i in range(runs):
voltages[i] = get_voltage(ad, ch)
timestamps[i] = time.time()
intervals = [timestamps[i] - timestamps[i-1] for i in range(1, len(timestamps))]
df_intervals = pd.DataFrame(intervals)
df_voltages = pd.DataFrame(voltages)
pd.options.display.float_format = '{:.6f}'.format
print(f"Interval\n{df_intervals.describe()}\n")
print(f"Voltage\n{df_voltages.describe()}")
except (IndexError, ValueError):
sys.exit(2)
finally:
spi.close()
計測のインターバルは 6ms で、サンプリング周期は 約166Hz でした。
1個4チャンネル
次に、ADPi Pro 1個の1~4チャンネルで計測します。
4ch_adpispi.py を作成して実行します。
4ch_adpispi.py 全体を見る
#!/usr/bin/python3
import spidev
import smbus
import adpi
import sys
from time import sleep
import time
import pandas as pd
RAW_OFFSET = (1 << 23)
RAW_SCALE = (
0.000596040,
0.000298020,
0.000149010,
0.000074500,
0.000037250,
0.000018620,
0.000009310,
0.000004650,
)
TEMP_VREF = 1.17
def v2k(rate, val):
for k, v in rate.items():
if v == val:
return k
def single_conversion(dev, ch):
c = dev.adc.channel[ch]
g, _ = dev.read_configuration()
dev.write_configuration(g, c)
_, r = dev.read_mode()
dev.write_mode(dev.adc.mode['single'], r)
while True:
if not dev.read_status() & 0x80:
break
raw = dev.read_data()
return raw, g
def set_calib(dev):
g = dev.adc.gain['1']
r = dev.adc.rate['470']
bias = dev.load_bias(g)
scale = dev.load_scale(g)
_, r = dev.read_mode()
for i in range(dev.channels):
dev.write_configuration(g, i)
dev.write_mode(dev.adc.mode['idle'], r)
dev.write_offset(bias[i])
dev.write_mode(dev.adc.mode['idle'], r)
dev.write_fullscale(scale[i])
def get_voltage(dev, ch):
raw, g = single_conversion(dev, ch)
return RAW_SCALE[g] * (raw - RAW_OFFSET)
if __name__ == "__main__":
spibus = 0
spics = 0
eeprombus = 1
eepromaddr = 0x57
gpiobus = 1
gpioaddr = 0x27
spi = spidev.SpiDev()
i2c = smbus.SMBus(eeprombus)
try:
spi.open(spibus, spics)
spi.mode = 0b11
spi.max_speed_hz = 1000000
ad = adpi.ADPiPro(spi, i2c, eepromaddr, gpioaddr)
set_calib(ad)
ch_list = ['1', '2', '3', '4']
runs = 100000
voltages = {ch: [0] * runs for ch in ch_list}
timestamps = [0] * runs
for i in range(runs):
for ch in ch_list:
voltages[ch][i] = get_voltage(ad, ch)
timestamps[i] = time.time()
intervals = [timestamps[i] - timestamps[i-1] for i in range(1, len(timestamps))]
df_intervals = pd.DataFrame(intervals)
df_voltages = pd.DataFrame(voltages)
pd.options.display.float_format = '{:.6f}'.format
print(f"Interval\n{df_intervals.describe()}\n")
print(f"Voltage\n{df_voltages.describe()}")
except (IndexError, ValueError):
sys.exit(2)
finally:
spi.close()
計測のインターバルは 23ms で、サンプリング周期は 約43Hz でした。
2個4チャンネル並列(計8チャンネル)
次に、ADPi Pro 2個の1~4チャンネルを並列に計測します。
dual_adpispi.py を作成して実行します。
dual_adpispi.py 全体を見る
#!/usr/bin/python3
import spidev
import smbus
import adpi
import sys
from time import sleep
import time
import pandas as pd
from threading import Thread, Event
RAW_OFFSET = (1 << 23)
RAW_SCALE = (
0.000596040,
0.000298020,
0.000149010,
0.000074500,
0.000037250,
0.000018620,
0.000009310,
0.000004650,
)
TEMP_VREF = 1.17
def v2k(rate, val):
for k, v in rate.items():
if v == val:
return k
def single_conversion(dev, ch):
c = dev.adc.channel[ch]
g, _ = dev.read_configuration()
dev.write_configuration(g, c)
_, r = dev.read_mode()
dev.write_mode(dev.adc.mode['single'], r)
while True:
if not dev.read_status() & 0x80:
break
raw = dev.read_data()
return raw, g
def set_calib(dev):
g = dev.adc.gain['1']
r = dev.adc.rate['470']
bias = dev.load_bias(g)
scale = dev.load_scale(g)
_, r = dev.read_mode()
for i in range(dev.channels):
dev.write_configuration(g, i)
dev.write_mode(dev.adc.mode['idle'], r)
dev.write_offset(bias[i])
dev.write_mode(dev.adc.mode['idle'], r)
dev.write_fullscale(scale[i])
def get_voltage(dev, ch):
raw, g = single_conversion(dev, ch)
return RAW_SCALE[g] * (raw - RAW_OFFSET)
def record_voltages(dev, ch_list, voltages, idx, start_event):
start_event.wait()
for ch in ch_list:
voltages[ch][idx] = get_voltage(dev, ch)
if __name__ == "__main__":
spibus = 0
cs_list = [0, 1]
eeprombus = 1
eepromaddr_list = [0x57, 0x56]
gpiobus = 1
gpioaddr_list = [0x27, 0x26]
spi = spidev.SpiDev()
i2c = smbus.SMBus(eeprombus)
try:
ad_list = [None] * len(cs_list)
for cs in cs_list:
spi.open(spibus, cs)
spi.mode = 0b11
spi.max_speed_hz = 1000000
ad = adpi.ADPiPro(spi, i2c, eepromaddr_list[cs], gpioaddr_list[cs])
set_calib(ad)
ad_list[cs] = ad
sleep(0.5)
ch_list = ['1', '2', '3', '4']
runs = 100000
voltages = {cs: {ch: [0] * runs for ch in ch_list} for cs in cs_list}
timestamps = [0] * runs
start_event = Event()
for i in range(runs):
threads = [None] * len(cs_list)
for cs in cs_list:
t = Thread(target=record_voltages, args=(ad_list[cs], ch_list, voltages[cs], i, start_event))
t.start()
threads[cs] = t
start_event.set()
for t in threads:
t.join(timeout=3)
start_event.clear()
timestamps[i] = time.time()
intervals = [timestamps[i] - timestamps[i-1] for i in range(1, len(timestamps))]
df_intervals = pd.DataFrame(intervals)
df_voltages_cs0 = pd.DataFrame(voltages[0])
df_voltages_cs1 = pd.DataFrame(voltages[1])
pd.options.display.float_format = '{:.6f}'.format
print(f"Intervals\n{df_intervals.describe()}\n")
print(f"Voltages CS0\n{df_voltages_cs0.describe()}")
print(f"Voltages CS1\n{df_voltages_cs1.describe()}")
except (IndexError, ValueError):
sys.exit(2)
finally:
spi.close()
計測のインターバルは 27ms で、サンプリング周期は 約37Hz でした。
おわりに
本記事では、ADPi Pro を2個使用する設定の紹介と、複数チャンネルの計測およびサンプリング周期を測定しました。
サンプリング周期の測定結果は次の通りです。
・1個1チャンネル 約166Hz
・1個4チャンネル 約43Hz
・2個4チャンネル並列(計8チャンネル) 約37Hz
1個の ADPi Pro で4チャンネルを計測する場合、サンプリング周期は1チャンネルの 約1/4 となりました。計測するチャンネル数が増えるため、妥当な結果と考えられます。
2個の ADPi Pro で4チャンネルを並列に計測する場合、各 ADPi Pro が個別に A/D 変換を行うため、サンプリング周期は1個4チャンネル計測する場合とほぼ変わらない結果となりました。
※ 測定結果は参考値であり、実際のサンプリング周期は使用環境やスクリプトの実装によって異なる可能性がありますので、ご了承ください。
以上、拙文ではございますが、最後までお読みいただきありがとうございました。