MECHATRAXのブログ

弊社製品やサービスに関するブログです。企画や開発の際の参考になれば幸いです。
ADPi Pro を使いこなす!~ 連続変換モード ~

ADPi Pro を使いこなす!~ 連続変換モード ~

2024.11.29@kiyonaga

はじめに

弊社製品のラズベリーパイ用高精度 A/D 変換モジュール「ADPi Pro」では、A/D 変換を都度行うシングル変換モードと、連続的に行う連続変換モードの2種類が使用できます。

通常、adpictl get voltage * コマンドではシングル変換モードが使用されており、多くのユーザはこちらに馴染みがあると思います。一方で、連続変換モードについては、情報が限られているため、まだ十分に活用されていないかもしれません。

そこで本記事では、連続変換モードに焦点を当て、設定のポイントやサンプリング周期の測定結果を紹介します。
まずは変換モードについて整理します。

変換モードの概要

シングル変換モード
・ 全4つのチャンネルを切り替えながら計測可能
・ セトリングタイム(信号が安定するまでの時間)が必要なため、設定した更新レートとサンプリング周期の乖離が大きい

連続変換モード
・ 単一チャンネルの使用に限定
・ セトリングタイムが不要なため、設定した更新レートとサンプリング周期の乖離が少ない

連続変換モードでは、更新レートに近いサンプリング周期が得られることが期待されます。
次に、実行環境や測定結果を見ていきます。

実行環境

ハードウェア

ソフトウェア

  • オペレーティングシステム
    Raspberry Pi OS Lite (64bit) Bookworm
  • 弊社提供パッケージ
    python3-adpi、adpi-utils-backend-spidev、adpi-utils-backend-iio、adpi-utils、adpi-dkms
    sleepi3-firmware、sleepi3-utils、sleepi3-monitor、python3-sleepi
  • Python パッケージ
    python3-pandas

注意事項
本記事では ADPi Pro と slee-Pi3 の基本的なセットアップ方法は紹介していません。必要に応じて、次の参考資料をもとにセットアップを行ってください。

セットアップ用の参考資料

ADPi
弊社 GitHub ページ
SPIを用いたラズベリーパイ用高精度A/D変換モジュール「ADPi Pro」のセットアップ
IIOサブシステムを用いたラズベリーパイ用高精度A/D変換モジュール「ADPi Pro」のセットアップ

slee-Pi3
弊社 GitHub ページ
ラズベリーパイ用電源管理/死活監視モジュール「slee-Pi3」のセットアップ

測定と結果

今回は、SPIDEV および IIO SUBSYSTEM の連続変換モードを使用し、更新レート上限の 470Hz でサンプリング周期を測定しました。
※ 測定の際に、ADPi Pro のチャンネル1に抵抗を配置し、電圧値が約 18mV となるように設定しています。

SPIDEV

次の手順で測定用スクリプトを実行します。

  1. 弊社 Github ページ を参考にパッケージをインストール
  2. sudo apt install python3-pandas を実行
  3. spi_ch1_continuous.py を作成し、実行
spi_ch1_continuous.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 continuous_setting(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['continuous'], r)
    rate = v2k(dev.adc.rate, r)
    return g, rate


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])


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)
        sleep(0.5)

        ch = '1'
        runs = 100000
        voltages = [0] * runs
        timestamps = [0] * runs

        g, rate = continuous_setting(ad, ch)
        sleep(0.5)

        for i in range(runs):
            while True:
                if not ad.read_status() & 0x80:
                    break

            raw = ad.read_data()
            voltages[i] = RAW_SCALE[g] * (raw - RAW_OFFSET)
            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()

結果は、計測のインターバルが 2ms で、サンプリング周期が約 470Hz でした。

設定やスクリプトのポイントは次の通りです。

  • 更新レートやゲインの設定は、OS 側の設定と合わせる必要がある
  • dev.write_mode(dev.adc.mode[‘continuous’], r) で連続変換モードに設定
    • シングル変換モードの場合は、dev.write_mode(dev.adc.mode[‘single’], r)

IIO SUBSYSTEM

次の手順で測定用スクリプトを実行します。

  1. 弊社 Github ページ を参考にパッケージをインストール
  2. sudo apt install python3-pandas を実行 ※ インストール済みの場合スキップ
  3. 2つのスクリプト(iio_ch1_buffering.sh と calc_statistics.py)を作成
  4. iio_ch1_buffering.sh を実行
iio_ch1_buffering.sh 全体を見る
#!/bin/bash
set -e

IIO_NAME=iio:device0
SPI_NAME=$(find /sys/bus/spi/devices/spi*/ -name "$IIO_NAME" | grep -o -E "spi[0-9]+\.[0-9]+")
SPI_ADDR=$(echo $SPI_NAME | cut -d= -f2)
IIO_PATH=$(find /sys/bus/spi/devices/${SPI_ADDR}/iio:device* -maxdepth 0)

INDEX_OFFSET=-1
CHANNEL=$((1 + $INDEX_OFFSET))
DUMP_FILE=/home/mtx/iio_dump_data
RUNS=100000

function start_buffering() {
  echo 1 | tee $IIO_PATH/scan_elements/in_voltage$1-voltage$1_en >/dev/null
  echo 1 | tee $IIO_PATH/scan_elements/in_timestamp_en >/dev/null
  echo 1 | tee $IIO_PATH/buffer/enable >/dev/null
}

function stop_buffering() {
  echo 0 | tee $IIO_PATH/buffer/enable >/dev/null
  echo 0 | tee $IIO_PATH/scan_elements/in_voltage$1-voltage$1_en >/dev/null
}

function run_hexdump() {
  start_buffering $CHANNEL

  hexdump -C "/dev/$IIO_NAME" > "$DUMP_FILE" &
  HEXDUMP_PID=$!

  while [ $(wc -l "$DUMP_FILE" | awk '{print $1}') -lt "$RUNS" ]
  do
    continue
  done

  kill "$HEXDUMP_PID" || true
  wait "$HEXDUMP_PID" || true
  stop_buffering $CHANNEL
}

run_hexdump
python3 ./calc_statistics.py $CHANNEL $IIO_NAME $SPI_NAME $DUMP_FILE $RUNS
calc_statistics.py 全体を見る
#!/usr/bin/env python3

import sys
import pandas as pd

def read_file(file_path):
    with open(file_path, 'r') as f:
        return f.read().strip()

def main(channel, iio_name, spi_name, dump_file, runs):
    base_path = f"/sys/bus/spi/devices/{spi_name}/{iio_name}"
    scale_path = f"{base_path}/in_voltage-voltage_scale"
    offset_path = f"{base_path}/in_voltage{channel}-voltage{channel}_offset"

    RAW_OFFSET = int(read_file(offset_path))
    RAW_SCALE = float(read_file(scale_path))

    data_hexes = []
    timestamp_hexes = []

    with open(dump_file, "r") as file:
        for line in file:
            data_hex = line[13:21].replace(" ", "")
            timestamp_hex = line[35:58].replace(" ", "")
            data_hexes.append(data_hex)
            timestamp_hexes.append(timestamp_hex)

    voltages = []
    for data_hex in data_hexes:
        data = int(data_hex, 16)
        voltage = RAW_SCALE * (data + RAW_OFFSET)
        voltages.append(voltage)

    timestamps = []
    for timestamp_hex in timestamp_hexes:
        timestamp_be = "".join(reversed([timestamp_hex[i:i+2] for i in range(0, len(timestamp_hex), 2)]))
        timestamp_ns = int(timestamp_be, 16)
        timestamps.append(timestamp_ns)

    voltages = voltages[-runs:]
    timestamps = timestamps[-runs:]

    intervals = [(timestamps[i] - timestamps[i-1]) / 1e9 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()}")

if __name__ == "__main__":
    if len(sys.argv) < 5:
        print("Usage: calc_statistics.py <CHANNEL> <IIO_NAME> <SPI_NAME> <DUMP_FILE> <RUNS>")
        sys.exit(1)

    channel = sys.argv[1]
    iio_name = sys.argv[2]
    spi_name = sys.argv[3]
    dump_file = sys.argv[4]
    runs = int(sys.argv[5])

    main(channel, iio_name, spi_name, dump_file, runs)

結果は、計測のインターバルが 2ms で、サンプリング周期が約 470Hz でした。

設定やスクリプトのポイントは次の通りです。

  • 設定時のファイル書き込みには管理者権限が必要
  • /sys/bus/spi/devices/spi0.*/iio:device0/scan_elements/in_voltage0-voltage0_en に 1 を書き込むと、チャンネル1での連続変換モードに設定
    • 設定後、/sys/bus/spi/devices/spi0.*/iio:device0/buffer/enable に 1 を書き込むと連続変換を開始する
    • /dev/iio:device* に対してデータの読み出しを行う

その他、設定の詳細などは 弊社 Github ページ をご参照ください。

おわりに

本記事では、SPIDEV および IIO SUBSYSTEM の連続変換モードを使用し、サンプリング周期を測定しました。

更新レート 470Hz でのサンプリング周期の測定結果は次の通りです。
・ SPIDEV 約470Hz
・ IIO SUBSYSTEM 約470Hz

設定した更新レートと実際のサンプリング周期にほとんど乖離は見られませんでした。
単一チャンネルでの測定において、測定速度も求められる場合は、連続変換モードをお試しください。

なお、今回の測定結果は参考値であり、実際のサンプリング周期は使用環境や実装方法によって異なる可能性があることをご了承ください。

※ シングル変換モード(SPIDEV)におけるサンプリング周期の測定結果については、別記事で紹介しています。

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

< ブログ一覧へ戻る

関連製品はこちら

関連記事はこちら