[[PageOutline]] = Yokogawa SMARTDAC+(GM/GPシリーズ)でのIOC作成 = [https://www.yokogawa.co.jp/solutions/services/oprex/oprex-measurement/oprex-data-acquisition/data-logger/modular-gm10/ Yokogawa SMARTDAC+(GMシリーズ)]用のIOCを立て続けに作ることになったので、その時の色々な経緯を書いておく。[[br]] どちらのシリーズもGX90xxのモジュールを使い、制御系もほぼ同じ様に使えるので、その参考になれば幸いである。[[br]] == cERLでMV2000をGP20に置き換え == cERLで使用していたMV2000は、IOCを落とすと本体の電源をON/OFFしない限り、IOCとの再接続ができないという問題を抱えていた。[[br]] 常時温度監視をするために使用していたので通常は問題ないが、何かの拍子にIOCが落ちたりすると、電源ON/OFFの事を忘れていたためにデータが取れていないということが何回かあったので、運転終了後に入れ替えることになった。[[br]] 候補としては、[https://www.chino.co.jp/ Chino]の[https://www.chino.co.jp/products/recorders/kr3000/ kr3000]等も考えたが、他の所で[https://www-linac.kek.jp/cont/epics/netdev/ netDev]のdarwinプロトコルを使用することでデータが取れることを聞いていたので、今回は[https://www.yokogawa.co.jp/solutions/services/oprex/oprex-measurement/oprex-data-acquisition/portable-data-acquisition/touch-screen-gp10-gp20/ Yokogawa GP20]を使用することにした。 測定ch構成は、 * 温度測定 48ch * 1~12 ch [https://www.chino.co.jp/products/sensors/r800/ chino 極低温工業用白金・コバルト測温抵抗体] * 13~48ch T型熱電対(銅コンスタンタン) * Alarm接点出力 なし 測温抵抗体は、測定電圧を変換表で換算することで温度を計算する仕様になっている。[[br]] この話は今回の本題ではないので割愛。 == netDev + Darwin == 始めにnetDevを環境にインストールする。[[br]] 今回は、[https://www-linac.kek.jp/cont/epics/netdev/]から1.0.4を取ってきた。(終わってから、J-PARCの山田さんが[https://github.com/shuei/netDev netDev 1.0.5]を作っていたことを知った。)[[br]] === 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 プロトコルでの使用例が直には見つからなかった。[[br]] 冷凍機グループの中西さんが使っていると聞いていたので、使用しているレコードをサンプルとしてもらってきた。[[br]] そのサンプルを元に作成した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で指定するようにした。[[br]] ソースコードを確認したところ、{{{channels}}}レコードは最大チャネル数は90chだったので、1レコードで取得できるのはこれが最大らしい。[[br]] 各チャネルのデータは{{{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の後で、[[br]] {{{ dbpf "$(USER):$(MODEL):VOL:CH001_050.MODE" "EL" }}} とすることで、各chの設定を読み込んでレコードに設定する。[[br]] この方法でGP20(50ch)は、問題なく通信できている。 == PF-AR RF温度測定システムの更新 == GP20のIOCは、更に多チャンネルを扱う、こちらの案件のための実験も兼ねていた。[[br]] こちらで使用するチャネル数は、{{{analog 140ch + DIO 6ch}}} と {{{analog 80ch + DIO 3ch}}} なので、{{{netDev + darwin}}}の最大チャネル数を超えている。[[br]] HWも拡張モジュールを使用しないと対応できないので、subユニットを2つ追加する構成にした。[[br]] 当初考えていた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->チャネル変換→拡張タイプを設定した。[[br]] とりあえず、一番最後の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") } }}} エラーにはなるが、最後の熱電対のデータが取れているので、複数レコードでも通信自体はできているようだ。[[br]] その後色々デバッグしてみるが、どうしても140ch分のデータが出てこない。[[br]] 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 }}} となっていた。[[br]] レコードの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 || - || という構成にするしかないようだ。[[br]] 但し、電源とEXTが一つずつ追加することになり、今更変更(追加購入)はしたくない(できない)とのこと。 一応、Yokogawaには問い合わせを行ったが、FWでこの問題を解決しない限り、DARWINプロトコルを使うユーザー側のソフトウェアでの解決できなさそう。[[br]] 結局は仕様ということになりそうだが、今の所{{{netdev+ darwin}}}で動作可能な以下の構成に変更して動作試験をしてみた。[[br]] || 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]}}}が発生する。[[br]] この状態で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モジュールには対応していない?[[br]] 最終的には設置場所や予算の関係で、当初の構成でないとダメだということになったので、netDev + darwin を諦めて、modbusで実装することにした。 === modbus === 以前modbusを使ったIOCを作成しようとしたが、色々と知識不足で断念した経緯があった。[[br]] 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を作成した。[[br]] 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}}}を使って、アクセスするレジスタや範囲、型を指定する方法らしい。 [https://cars9.uchicago.edu/software/epics/modbusDoc.html#Creating_a_modbus_port_driver Creating a modbus port driver] 更に話がややこしいのが、GM用のmodbusレジスタの設定。 [https://web-material3.yokogawa.com/IM04L55B01-01JA.pdf?_ga=2.35954336.1696056082.1563237256-1465924258.1537346367 データアクイジションシステム GM ユーザーズマニュアル ] マニュアルを見れば一応書いてはあるが、マニュアル中のレジスタマップにあるレジスタ番号をそのまま使うのではなく、レジスタマップの一番最初の数値をオフセットとして引いた値をアドレスとして使用する必要がある。[[br]] 始めは、レジスタ値をそのまま書いていたが、アドレス設定が16bit分しかないのに何でマニュアルのアドレス値がそれをオーバーしているのかが不思議だった。結局マニュアルをしっかり読めということだ。[[br]] この2つの設定のどことどこが対応するかを理解するまでが苦労した。[[br]] 実際の例として、今回の構成で各モジュールへのレジスタへアクセスするには、以下の様に設定する。 || 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 || - || mainの3番モジュール(AI) * 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}}}を、マニュアルのファンクションコードに合わせること [[Image(GM_function_table.png,400)]] * {{{drvModbusAsynConfigure}}}の5番目の引き数{{{1100}}}は、レジスタマップを参照することになるが、今回例示するモジュールのレジスタは、下図の{{{チャネル0101のデータの下位ワード - 0x400001}}}なので、{{{1100}}}を設定する [[Image(GM_modbus_addr_table.png,400)]] * modbusのデータは16bit単位なので、連続するアドレスにアクセスして32bit浮動小数点に分解する場合には2bitずつoffsetを設定し、アクセスするデータ長も2倍で設定する。({{{drvModbusAsynConfigure}}}の6番目の引き数{{{20}}}) * {{{drvModbusAsynConfigure}}}の7番目の引き数{{{7}}}は、modbus device support の{{{FLOAT32_LE}}}を表していて、DBファイルのINPフィールドの{{{$(TYPE)}}}の部分と合わせる必要がある。 この設定を全てのモジュールに対して記述すれば、各チャネルのデータを取得できるようになる。