MECHATRAXのブログ

弊社製品やサービスに関するブログです。企画や開発の際の参考になれば幸いです。
ラズパイ1個で ADPi Pro 2個を使う設定と複数チャンネルでの計測

ラズパイ1個で ADPi Pro 2個を使う設定と複数チャンネルでの計測

2024.09.13@kiyonaga

はじめに

タイトルの通り、高精度A/D変換モジュール ADPi Pro はラズベリーパイ1個につき最大2個まで使用可能です。

ADPi Pro は1個あたり4チャンネルの信号入力が可能です。2個使用することで、信号入力可能なチャンネル数を8チャンネルに増設できます。
これにより、1個の ADPi Pro では不足するチャンネル数を補うことができます。

今回は ADPi Pro 2個を設定し、SPIDEV を使用して複数チャンネルでのデータ計測およびサンプリング周期を測定してみます。

実行環境

ハードウェア

ソフトウェア

  • オペレーティングシステム
    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チャンネル計測する場合とほぼ変わらない結果となりました。

※ 測定結果は参考値であり、実際のサンプリング周期は使用環境やスクリプトの実装によって異なる可能性がありますので、ご了承ください。

以上、拙文ではございますが、最後までお読みいただきありがとうございました。

< ブログ一覧へ戻る

関連製品はこちら

関連記事はこちら