Yokogawa SMARTDAC+(GM/GPシリーズ)でのIOC作成
Yokogawa SMARTDAC+(GMシリーズ)用のIOCを立て続けに作ることになったので、その時の色々な経緯を書いておく。
どちらのシリーズもGX90xxのモジュールを使い、制御系もほぼ同じ様に使えるので、その参考になれば幸いである。
cERLでMV2000をGP20に置き換え
cERLで使用していたMV2000は、IOCを落とすと本体の電源をON/OFFしない限り、IOCとの再接続ができないという問題を抱えていた。
常時温度監視をするために使用していたので通常は問題ないが、何かの拍子にIOCが落ちたりすると、電源ON/OFFの事を忘れていたためにデータが取れていないということが何回かあったので、運転終了後に入れ替えることになった。
候補としては、Chinoのkr3000等も考えたが、他の所でnetDevのdarwinプロトコルを使用することでデータが取れることを聞いていたので、今回はYokogawa GP20を使用することにした。
測定ch構成は、
- 温度測定 48ch
- 1~12 ch chino 極低温工業用白金・コバルト測温抵抗体
- 13~48ch T型熱電対(銅コンスタンタン)
- Alarm接点出力 なし
測温抵抗体は、測定電圧を変換表で換算することで温度を計算する仕様になっている。
この話は今回の本題ではないので割愛。
netDev + Darwin
始めにnetDevを環境にインストールする。
今回は、https://www-linac.kek.jp/cont/epics/netdev/から1.0.4を取ってきた。(終わってから、J-PARCの山田さんがnetDev 1.0.5を作っていたことを知った。)
netDevのインストール
$ cd インストールディレクトリ $ wget https://www-linac.kek.jp/cont/epics/netdev/netDev-1.0.4.tar.gz $ tar zxvf netDev-1.0.4.tar.gz $ mv netDev-1.0.4 1.0.4 $ cd 1.0.4 $ emacs -nw configure/RELEASE EPICS_BASE=/cerl/epics/R315.5/base $ make
IOC
実際にIOCを作成するにあたり、netDev上のdarwin プロトコルでの使用例が直には見つからなかった。
冷凍機グループの中西さんが使っていると聞いていたので、使用しているレコードをサンプルとしてもらってきた。
そのサンプルを元に作成したDBファイル。
# Yokogawa GP10 Channel 001 ~ 050 record(channels, "$(USER):$(MODEL):VOL:CH001_050") { field(SCAN, "$(SCAN)") field(DTYP, "Darwin") field(NELM, "50") field(INP, "@$(IP)($(PORT))#001,050") # @IP(port)#CHs~CHe }
$(PORT)番号は34151で固定だが、$(IP)と共にst.cmdで指定するようにした。
ソースコードを確認したところ、channelsレコードは最大チャネル数は90chだったので、1レコードで取得できるのはこれが最大らしい。
各チャネルのデータはCHxxフィールドから個別に取得できるようになるので、通常は他のレコードにリンクして使用する。
実際にIOCを作る際には他のモジュールと同様に、configure/RELEASEとxxxApp/src/Makefileを修正する。
- configure/RELEASE
: NETDEV=$(EPICS_BASE)/../modules/soft/netDev/1.0.4 :
- xxxApp/src/Makefile
: ifneq ($(NETDEV),) xxxx_DBD += netDev.dbd xxxx_LIBS += netDev endif :
st.cmdにはiocInitの後で、
dbpf "$(USER):$(MODEL):VOL:CH001_050.MODE" "EL"
とすることで、各chの設定を読み込んでレコードに設定する。
この方法でGP20(50ch)は、問題なく通信できている。
PF-AR RF温度測定システムの更新
GP20のIOCは、更に多チャンネルを扱う、こちらの案件のための実験も兼ねていた。
こちらで使用するチャネル数は、analog 140ch + DIO 6ch と analog 80ch + DIO 3ch なので、netDev + darwinの最大チャネル数を超えている。
HWも拡張モジュールを使用しないと対応できないので、subユニットを2つ追加する構成にした。
当初考えていたHW構成は、
slot | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
Main | CPU | DO | AI | AI | AI | AI | EXT |
Sub1 | EXT | AI | AI | AI | AI | AI | EXT |
Sub2 | EXT | AI | AI | AI | AI | AI | - |
GMのWeb画面から、通信(イーサネット)設定->サーバ設定 サーバリスト->DARWIN->チャネル変換→拡張タイプを設定した。
とりあえず、一番最後のchに熱電対を取り付けて実験してみた。
netDev + darwin
1チャネルの数を増やさなくても、ユニット毎にレコードを設定すればデータの取得が可能なのではないかと考えた。
record(channels, "$(user):GM:CH001050") { field(SCAN, "$(SCAN)") field(DTYP, "Darwin") field(INP, "@$(IP)($(PORT))#001,050") # @IP(port)#CHs~CHe field(NELM, "50") field(FLNK, "$(user):GM:CH100150") } record(channels, "$(user):GM:CH100150") { field(DTYP, "Darwin") field(INP, "@$(IP)($(PORT))#100,150") # @IP(port)#CHs~CHe field(NELM, "50") }
エラーにはなるが、最後の熱電対のデータが取れているので、複数レコードでも通信自体はできているようだ。
その後色々デバッグしてみるが、どうしても140ch分のデータが出てこない。
tcpdumpでELコマンド(EL001,250)の出力を見てみると、
001 C ,1 002 C ,1 003 C ,1 004 C ,1 005 C ,1 006 C ,1 007 C ,1 008 C ,1 009 C ,1 010 C ,1 : 049 C ,1 050 C ,1 101 C ,1 102 C ,1 : 149 C ,1 E150 C ,1
となっていた。
レコードのINPフィールドのch番号を色々と変更しても、このch番号に変化はない。
HW構成を色々と変えてみて、変化を確認してみた。
- DO moduleなし
- 変わらず
slot 1 2 3 4 5 6 7 main CPU AI AI AI AI - EXT sub1 EXT AI AI AI AI AI EXT sub2 EXT AI AI AI AI AI -
- DO moduleなし,EXT module変更
- 変わらず
slot 1 2 3 4 5 6 7 main CPU AI AI AI AI EXT - sub1 EXT AI AI AI AI AI EXT sub2 EXT AI AI AI AI AI -
- sub1,sub2なし
- サーバー設定 サーバーリストのDarwin設定をstandaloneに設定すると40chが見えた。
- 設定を変更しないとエラー(E1)
slot 1 2 3 4 5 6 7 main CPU AI AI AI AI - -
- DOユニットなし
- darwin設定が standaloneままだと40chしか見えない
- darwin設定が 拡張設定だと 50ch + 50ch
slot 1 2 3 4 5 6 7 main CPU AI AI AI AI - EXT sub1 EXT AI AI AI AI AI EXT sub2 EXT AI AI AI AI AI -
このことから、HW構成を変更することで対処しようとすると、
slot 1 2 3 4 5 6 7 main CPU DO EXT - - - - sub1 EXT AI AI AI AI EXT - sub2 EXT AI AI AI AI AI EXT sub3 EXT AI AI AI AI AI -
という構成にするしかないようだ。
但し、電源とEXTが一つずつ追加することになり、今更変更(追加購入)はしたくない(できない)とのこと。
一応、Yokogawaには問い合わせを行ったが、FWでこの問題を解決しない限り、DARWINプロトコルを使うユーザー側のソフトウェアでの解決できなさそう。
結局は仕様ということになりそうだが、今の所netdev+ darwinで動作可能な以下の構成に変更して動作試験をしてみた。
slot 1 2 3 4 5 6 7 main CPU DO EXT - - - - sub1 EXT AI AI AI AI EXT - sub2 EXT AI AI AI AI AI EXT sub3 EXT AI AI AI AI AI -
レコードはこのようなものにした。
record(channels, "$(user):GM:CH001040") { field(SCAN, "$(SCAN)") field(DTYP, "Darwin") field(INP, "@$(IP)($(PORT))#001,040") # @IP(port)#CHs~CHe field(NELM, "40") field(FLNK, "$(user):GM:CH101150") } record(channels, "$(user):GM:CH101150") { field(DTYP, "Darwin") field(INP, "@$(IP)($(PORT))#101,150") # @IP(port)#CHs~CHe field(NELM, "50") field(FLNK, "$(user):GM:CH201250") } record(channels, "$(user):GM:CH201250") { field(DTYP, "Darwin") field(INP, "@$(IP)($(PORT))#201,250") # @IP(port)#CHs~CHe field(NELM, "50") }
st.cmdには、iocInit後に以下のコマンドを実行して、HW設定を読み込ませる。
dbpf "$(USER):GM:CH001040.MODE" "EL" epicsThreadSleep 1 dbpf "$(USER):GM:CH101150.MODE" "EL" epicsThreadSleep 1 dbpf "$(USER):GM:CH201250.MODE" "EL"
epicsThreadSleepを入れないとdrvNetMpf: send error [32]が発生する。
この状態でIOCを起動すると、エラーも出なくなって正常に動作する。
試しに、DO moduleをsub1に移動して、レコードも追加して実行してみた。
slot 1 2 3 4 5 6 7 main CPU EXT - - - - - sub1 EXT DO AI AI AI AI EXT sub2 EXT AI AI AI AI AI EXT sub3 EXT AI AI AI AI AI -
record(channels, "$(user):GM:CH001006") { field(SCAN, "$(SCAN)") field(DTYP, "Darwin") field(INP, "@$(IP)($(PORT))#001,006") # @IP(port)#CHs~CHe field(NELM, "6") field(FLNK, "$(user):GM:CH011050") }
この状態で実行すると、以前と同じでエラーになってしまう。
drvNetMpf: *** mpf_timeout_handler("michkawa:pfarthrm:GM:CH011050"):2019.07/09 15:42:32.769832 *** drvNetMpf: *** mpf_timeout_handler("michkawa:pfarthrm:GM:CH001006"):2019.07/09 15:42:34.764932 *** parse_chan_response: Data Length dose not match: 8224, 208, 50, 0("michkawa:pfarthrm:GM:CH101150") drvNetMpf: *** mpf_timeout_handler("michkawa:pfarthrm:GM:CH011050"):2019.07/09 15:42:36.761678 *** parse_chan_response: Data Length dose not match: 8224, 208, 50, 0("michkawa:pfarthrm:GM:CH201250") drvNetMpf: *** mpf_timeout_handler("michkawa:pfarthrm:GM:CH001006"):2019.07/09 15:42:38.758844 *** parse_chan_response: Data Length dose not match: 8, 208, 50, 0("michkawa:pfarthrm:GM:CH101150") drvNetMpf: *** mpf_timeout_handler("michkawa:pfarthrm:GM:CH011050"):2019.07/09 15:42:40.755598 *** parse_chan_response: Data Length dose not match: 8224, 208, 50, 0("michkawa:pfarthrm:GM:CH201250") drvNetMpf: *** mpf_timeout_handler("michkawa:pfarthrm:GM:CH001006"):2019.07/09 15:42:42.752343 ***
netdev + darwinはDOモジュールには対応していない?
最終的には設置場所や予算の関係で、当初の構成でないとダメだということになったので、netDev + darwin を諦めて、modbusで実装することにした。
modbus
以前modbusを使ったIOCを作成しようとしたが、色々と知識不足で断念した経緯があったので再挑戦。
始めにmodbusモジュールをPF EPICS環境にインストール。
[epics@pfrproc1 ~]$ cd /pf/epics/R315.5/modules/soft/ [epics@pfrproc1 soft]$ mkdir modbus [epics@pfrproc1 soft]$ cd modbus/ [epics@pfrproc1 modbus]$ wget https://github.com/epics-modules/modbus/archive/R2-10-1.tar.gz [epics@pfrproc1 modbus]$ tar zxvf R2-10-1.tar.gz [epics@pfrproc1 modbus]$ mv modbus-R2-10-1 2-10-1 [epics@pfrproc1 modbus]$ cd 2-10-1 [epics@pfrproc1 2-10-1]$ emacs -nw configure/RELEASE #SUPPORT=/corvette/home/epics/devel #-include $(TOP)/../configure/SUPPORT.$(EPICS_HOST_ARCH) #ASYN=$(SUPPORT)/asyn-4-31 ASYN=$(EPICS_BASE)/../modules/soft/asyn/4-31/ #EPICS_BASE=/corvette/usr/local/epics-devel/base-3.15.5 EPICS_BASE=/pf/epics/R315.5/base [epics@pfrproc1 2-10-1]$ make
次にiocを作成した。
modbusはasynPortDriverを使うようなので、asynも有効にする。
[michkawa@pfrproc6 ~]$ mkdir -p epics/modbus/pfarthrm/ [michkawa@pfrproc6 ~]$ cd epics/modbus/pfarthrm/ [michkawa@pfrproc6 pfarthrm]$ makeBaseApp.pl -t pfModules pfarthrm [michkawa@pfrproc6 pfarthrm]$ makeBaseApp.pl -i -t pfModules pfarthrm [michkawa@pfrproc6 pfarthrm]$ emacs -nw configure/RELEASE ASYN = $(EPICS_BASE)/../modules/soft/asyn/4-31 #STREAM = $(EPICS_BASE)/../modules/soft/stream/2-7-7 MODBUS = $(EPICS_BASE)/../modules/soft/modbus/2-10-1 EPICS_BASE = /pf/epics/R315.5/base
src/Makefileには、
ifneq ($(MODBUS),) pfarthrm_DBD += modbusSupport.dbd pfarthrm_LIBS += modbus endif
を追加して、asynで必要なdriverを設定する。 今回はIPPortドライバなので、
pfarthrm_DBD += drvAsynIPPort.dbd # IP Port Driver
を設定。
dbファイルは、普通のasynPortDriverの書き方。
record(ai, "$(R)") { field(DTYP, "asynFloat64") field(INP, "@asyn($(PORT) $(OFFSET))$(TYPE)") field(SCAN, "1 second") field(FLNK, "$(FLNK)") }
ややこしいのがst.cmdの書き方で、drvModbusAsynConfigureを使って、アクセスするレジスタや範囲、型を指定する方法らしい。
Creating a modbus port driver
更に話がややこしいのが、GM用のmodbusレジスタの設定。
マニュアルを見れば一応書いてはあるが、マニュアル中のレジスタマップにあるレジスタ番号をそのまま使うのではなく、レジスタマップの一番最初の数値をオフセットとして引いた値をアドレスとして使用する必要がある。
始めは、レジスタ値をそのまま書いていたが、アドレス設定が16bit分しかないのに何でマニュアルのアドレス値がそれをオーバーしているのかが不思議だった。結局マニュアルをしっかり読めということだ。
この2つの設定のどことどこが対応するかを理解するまでが苦労した。
実際の例として、今回の構成で各モジュールへのレジスタへアクセスするには、以下の様に設定する。(カッコ内はモジュール番号)
slot | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
Main | CPU | DO(0001) | AI(0101) | AI(0102) | AI(0103) | AI(0104) | EXT |
Sub1 | EXT | AI(0201) | AI(0202) | AI(0203) | AI(0204) | AI(0205) | EXT |
Sub2 | EXT | AI(0301) | AI(0302) | AI(0303) | AI(0304) | AI(0305) | - |
mainの3番モジュール(AI,Module No:0101)
- db(template)
record(ai, "$(R)") { field(DTYP, "asynFloat64") field(INP, "@asyn($(PORT) $(OFFSET))$(TYPE)") field(SCAN, "1 second") field(FLNK, "$(FLNK)") }
- db(substitutions)
file "db/Ykgw_GM_ai.template" { pattern { R, PORT, OFFSET, TYPE, FLNK } {"RFA:ARE:GM90:0101", "GM_E1_A1_2", 0, "FLOAT32_LE", "RFA:ARE:GM90:CAV1:WTMPIN"} {"RFA:ARE:GM90:0102", "GM_E1_A1_2", 2, "FLOAT32_LE", "RFA:ARE:GM90:CAV1:WTMPO"} {"RFA:ARE:GM90:0103", "GM_E1_A1_2", 4, "FLOAT32_LE", "RFA:ARE:GM90:CAV1:CTMP"} {"RFA:ARE:GM90:0104", "GM_E1_A1_2", 6, "FLOAT32_LE", "RFA:ARE:GM90:MASK_SIC:WTMPIN"} {"RFA:ARE:GM90:0105", "GM_E1_A1_2", 8, "FLOAT32_LE", "RFA:ARE:GM90:MASK_SIC:WTMPO"} {"RFA:ARE:GM90:0106", "GM_E1_A1_2", 10, "FLOAT32_LE", "RFA:ARE:GM90:MASK:WTMPO"} {"RFA:ARE:GM90:0107", "GM_E1_A1_2", 12, "FLOAT32_LE", "RFA:ARE:GM90:CAV2:WTMPIN"} {"RFA:ARE:GM90:0108", "GM_E1_A1_2", 14, "FLOAT32_LE", "RFA:ARE:GM90:CAV2:WTMPO"} {"RFA:ARE:GM90:0109", "GM_E1_A1_2", 16, "FLOAT32_LE", "RFA:ARE:GM90:CAV2:CTMP"} {"RFA:ARE:GM90:0110", "GM_E1_A1_2", 18, "FLOAT32_LE", ""} :
- st.cmd
drvAsynIPPortConfigure ("$(DEV)", "172.28.14.101:502") modbusInterposeConfig("$(DEV)", 0, 1000,0) drvModbusAsynConfigure("GM_E1_A1_2", "$(DEV)", 1, 4, 1100, 20, 7, 1000, "GM_E1")
この設定で必要なのは、
- drvModbusAsynConfigureの4番目の引き数4を、マニュアルのファンクションコードに合わせること
- drvModbusAsynConfigureの5番目の引き数1100は、レジスタマップから該当するものを探す。
今回例示するモジュール(0101)のレジスタは、下図のチャネル0101のデータの下位ワード - 0x300001なので、1100を設定する
- modbusのデータは16bit単位なので、連続するアドレスにアクセスして32bit浮動小数点に分解する場合には2bitずつoffsetを設定し、アクセスするデータ長も2倍で設定する。
- drvModbusAsynConfigureの6番目の引き数20は、1行のdrvModbusAsynConfigureで必要な16bit単位でのバッファ個数。今回は、1設定あたり 2*10なので、20を設定する。
- drvModbusAsynConfigureの7番目の引き数7は、modbus device support のFLOAT32_LEを表していて、DBファイルのINPフィールドの$(TYPE)の部分と合わせる必要がある。
この設定を全てのモジュールに対して記述すれば、各チャネルのデータを取得できるようになる。
Attachments (2)
- GM_function_table.png (96.2 KB) - added by michkawa 5 years ago.
- GM_modbus_addr_table.png (40.1 KB) - added by michkawa 5 years ago.
Download all attachments as: .zip