JF1DIR業務日誌(はてなblog版)

アマチュア無線局JF1DIRのアクティビティをつづっています。

Pythonで測定器の制御〜GPIB編〜

ひさびさのブログ更新は備忘録がわりの記事です。アマチュア無線については最近アクティビティがほとんどゼロになっているので何も書くことがありません。
お仕事(化学会社で研究職をやっています)で古い(実はそうでもないですが)分析機器を使っていますが、ほぼすべての分析機器・測定器は制御を自動化したりデータを取り込んだりするためにコンピュータと接続されています。よくあるトラブルが本体の測定装置よりもコンピュータのほうが先に故障してしまうことで、しかもハードやOSが古いせいで修理不能であったり、かといってコンピュータを新しくするとインタフェイスボードや制御ソフトウェアまで更新せざるをえなくなり、さらに新しい制御ソフトウエアは手持ちの古い測定に対応していなかったりと、たかだか数万円のコンピュータが壊れただけなのに、全部新しくしなければならない、その総費用は数千万円になることがザラにあったりして、ものすごく不合理です。
現在仕事で頻繁に使っている装置も、コンピュータ(DELLのPC、OSはWindows Vista)の動作が怪しくなってきており、そろそろ更新したいのだけど、上記のような事情でPCだけの更新は無理になりました。そこで、装置の制御をLinux上のPCで自力でやることにしました。
この装置は、ContecのPCIボードのGPIBアダプタによってGPIBで制御されているですが、ContecのデバイスドライバWindows用しかないことが判明(Linuxもあるけど古いカーネルにしか対応していない)。そこで、Agilent 82357bというUSB-GPIBアダプタを使うことにしました。このアダプタは結構有名なので、Linux用のドライバが用意されていました。以下、このアダプタを使ってLinux(Ubuntu 18.04 LTS, カーネル 5.3.0)上で装置を動かすまでの手順を示します。

Linux-GPIBのインストール

Linux-GPIBのWebページsourceforge.net
で最新のバージョンのソースファイルをダウンロードしてきます(今日現在 バージョン4.3.3)。次に、ソースを展開し、インストールします。

$ tar xzvf linux-gpib-4.3.3.tar.gz
$ cd linux-gpib-4.3.3
$ tar xzvf linux-gpib-kernel-4.3.3.tar.gz
$ cd linux-gpib-kernel-4.3.3
$ make
$ sudo make install
$ cd ../
$ tar xzvf linux-gpib-user-4.3.3.tar.gz
$ cd linux-gpib-user-4.3.3
$ ./configure
$ make
$ sudo make install

pythonとバインドするため開発ツールが入っていない場合はあらかじめ、

$ sudo apt install python3-dev libboost-dev python3-setuptools

としておきます(そうでないとmake時にエラーが出ます)。

アダプタのファームウェアの用意

Agilentのファームウェアはここのページにあるので、最新のものをダウンロードします(gpib_firmware-2008-08-10.tar.gz)。

$ tar xvzf gpib_firmware-2008-08-10.tar.gz
$ cd linux_gpib_firmware-master/agilent_82357a/
$ ls
82357a_fw.hex  firmware.c         lsusb_postload.txt
README         lsusb_initial.txt  measat_releaseX1.8.hex

このmeasat_releaseX1.8.hexがファームウェア本体となっています。

fxloadのインストール

入ってなければapt installでインストールしておきます。

gpib.confの設定

GPIBのインタフェイスとGPIB機器の登録を済ませておきます。場所は/usr/local/etc/gpib.confです。interfaceの節にAgilent 82357a、device節に各GPIB機器の機器名やプライマリーアドレスなどを設定しておきます。

interface {
        minor = 0       /* board index, minor = 0 uses /dev/gpib0, minor = 1 uses /dev/gpib1, etc. */
        board_type = "agilent_82357a"   /* type of interface board being used */
        name = "agi"    /* optional name, allows you to get a board descriptor using ibfind() */
        pad = 0 /* primary address of interface             */
        sad = 0 /* secondary address of interface           */
        timeout = T3s   /* timeout for commands */
        eos = 0x0a      /* EOS Byte, 0xa is newline and 0xd is carriage return */
        set-reos = yes  /* Terminate read if EOS */
        set-bin = no    /* Compare EOS 8-bit */
        set-xeos = no   /* Assert EOI whenever EOS byte is sent */
        set-eot = yes   /* Assert EOI with last byte on writes */
        base = 0        /* Base io ADDRESS                  */
        irq  = 0        /* Interrupt request level */
        dma  = 0        /* DMA channel (zero disables)      */
        master = yes    /* interface board is system controller */
}

device {
        minor = 0       /* minor number for interface board this device is connected to */
        name = "agilent_34401a" /* device mnemonic */
        pad = 12        /* The Primary Address */
        sad = 0 /* Secondary Address */
}

カーネルモジュールのロード

$ sudo modprobe gpib_common
$ sudo modprobe agilent_82357a

USBデバイスの設定

PCにUSBを差し込むと、アダプタの赤LEDのみ点灯しているはずです。USBバス番号を確認しておきます。

$ lsusb
Bus 001 Device 009: ID 0957:0718 Agilent Technologies, Inc. 
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 003: ID 413c:2107 Dell Computer Corp. 
Bus 003 Device 002: ID 413c:3200 Dell Computer Corp. Mouse

バス番号001、デバイス番号009でアダプタが認識されているのを確認しました。

fxloadでプラグする

$ sudo fxload -D /dev/bus/usb/001/009 -t fx2 -I XXXXX/measat_releaseX1.8.hex

XXXXXはファームウェアがあるディレクトリです。再度USBバス番号をチェックすると、

$ lsusb
Bus 001 Device 010: ID 0957:0718 Agilent Technologies, Inc. 
.....

なぜかデバイス番号が一つ増えています。もう一度、fxloadします。うまく行けば、アダプタの緑のLEDが点灯します(この時点では、赤と緑の両方のLEDが点灯しているはず)。

$ sudo fxload -D /dev/bus/usb/001/010 -t fx2 -I XXXXX/measat_releaseX1.8.hex

GPIBデバイスファイルの権限を変更

/dev/gpib0を通じて制御できるようになるのですが、rootのものなのでアクセス権を付与しておきます。

$ sudo chmod 666 /dev/gpib0

ライブラリのシンボリックリンクを張る

makeして作られたライブラリが/usr/local/lib/の下にできるのだが、実行時には/libのライブラリを見に行くので、シンボリックリンクを張っておく(バグでしょうか?)。

$ sudo ln -s /usr/local/lib/libgpib.so.0 /lib/libgpib.so.0

GPIBの設定を反映させる

$ sudo gpib_conf

うまくいけば、アダプタのLEDが緑だけ点灯しているはずで、これでようやく使えるようになりました。やれやれ。

動作チェック

ibtestというGPIBアダプタとGPIB機器をチェックしたりかんたんな通信ができるツールがあるので、実行してみます。

$ ibtest
Do you wish to open a (d)evice or an interface (b)oard?
	(you probably want to open a device): d
enter primary gpib address for device you wish to open [0-30]: 12
trying to open pad = 12 on /dev/gpib0 ...
You can:
	w(a)it for an event
	write (c)ommand bytes to bus (system controller only)
	send (d)evice clear (device only)
	change remote (e)nable line (system controller only)
	(g)o to standby (release ATN line, system controller only)
	send (i)nterface clear (system controller only)
	ta(k)e control (assert ATN line, system controller only)
	get bus (l)ine status (board only)
	go to local (m)ode
	change end (o)f transmission configuration
	(q)uit
	(r)ead string
	perform (s)erial poll (device only)
	change (t)imeout on io operations
	request ser(v)ice (board only)
	(w)rite data string
	send group e(x)ecute trigger (device only)
: 

のように動かせます。例えば、DMM(キーサイトの34401A)を使って電圧値を取得したい場合は、

: w
enter a string to send to your device: read?
sending string: read?
......
: r
enter maximum number of bytes to read [1024]: 1024
trying to read 1024 bytes from device...
received string: '-1.02559000E-04
'
Number of bytes read: 16
gpib status is: 
ibsta = 0x2100  < END CMPL >
iberr= 0
ibcntl = 16

のように、wコマンドで文字列を送信して("read?")、rコマンドで文字列を受信する("-1.02559000E-04\r\n")という具合です。

Pythonで制御

PythonでGPIBを制御する方法は、Linux-GPIBに付属しているライブラリとVISAライブラリを使う方法がありますが、今回は前者を使ってみます。例として、DMMと接続してトリガ測定で10回連続して電圧値を取得するスクリプトを作ってみました。

import gpib
import time

dev = gpib.find("agilent_34401a")

gpib.write(dev, "*rst")
time.sleep(0.1)
gpib.write(dev, "*cls")
time.sleep(0.1)
gpib.write(dev, "trig:coun 10")
time.sleep(0.1)
gpib.write(dev, "init")
time.sleep(1)
gpib.write(dev, "fetc?")
time.sleep(0.1)
str = gpib.read(dev, 1024)
print(str.decode().strip())

バッファから取り出した値はバイト型になって扱いづらいので、.decode()して文字列型に変換する必要があります。.strip()で行末の改行文字などを取り除いておきました。通信コマンドを受け付けるまで若干の時間を要するので、各コマンド間に0.1秒のスリープを設けました。実行結果はこのような結果になります。

+2.40874090E+01,+2.40881940E+01,+2.40894820E+01,+2.40883620E+01,+2.40887800E+01,+2.40903930E+01,+2.40902050E+01,+2.40898490E+01,+2.40905400E+01,+2.40893980E+01

次はUSB接続、シリアル接続についても説明します(備忘録として)。