MECHATRAXのブログ
弊社製品やサービスに関するブログです。企画や開発の際の参考になれば幸いです。
ADPi Pro を使いこなす!~ 連続変換モード ~
ADPi Pro を使いこなす!~ 連続変換モード ~
はじめに
弊社製品のラズベリーパイ用高精度 A/D 変換モジュール「ADPi Pro」では、A/D 変換を都度行うシングル変換モードと、連続的に行う連続変換モードの2種類が使用できます。
通常、adpictl get voltage *
コマンドではシングル変換モードが使用されており、多くのユーザはこちらに馴染みがあると思います。一方で、連続変換モードについては、情報が限られているため、まだ十分に活用されていないかもしれません。
そこで本記事では、連続変換モードに焦点を当て、設定のポイントやサンプリング周期の測定結果を紹介します。
まずは変換モードについて整理します。
変換モードの概要
シングル変換モード
・ 全4つのチャンネルを切り替えながら計測可能
・ セトリングタイム(信号が安定するまでの時間)が必要なため、設定した更新レートとサンプリング周期の乖離が大きい
連続変換モード
・ 単一チャンネルの使用に限定
・ セトリングタイムが不要なため、設定した更新レートとサンプリング周期の乖離が少ない
連続変換モードでは、更新レートに近いサンプリング周期が得られることが期待されます。
次に、実行環境や測定結果を見ていきます。
実行環境
ハードウェア
- Raspberry Pi 4 Model B x 1個
- Micro SD カード 32GB x 1枚
- ADPi Pro x 1個
- 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-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
次の手順で測定用スクリプトを実行します。
- 弊社 Github ページ を参考にパッケージをインストール
sudo apt install python3-pandas
を実行- 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 側の設定と合わせる必要がある
- OS 側の設定は adpictl コマンドで変更可能
- dev.write_mode(dev.adc.mode[‘continuous’], r) で連続変換モードに設定
- シングル変換モードの場合は、dev.write_mode(dev.adc.mode[‘single’], r)
IIO SUBSYSTEM
次の手順で測定用スクリプトを実行します。
- 弊社 Github ページ を参考にパッケージをインストール
sudo apt install python3-pandas
を実行 ※ インストール済みの場合スキップ- 2つのスクリプト(iio_ch1_buffering.sh と calc_statistics.py)を作成
- 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)におけるサンプリング周期の測定結果については、別記事で紹介しています。
以上、拙文ではございますが、最後までお読みいただきありがとうございました。