目次
チノー株式会社のネットワークロガーKEシリーズは、高速・多点のデータ集録に適したネットワーク対応のロガーです。各機能はユニット化されており、必要なユニットを組み合わせることで用途に応じたシステムを構築出来ます。具体的なシステムに関する情報は、メーカーのページをご参照ください。
本デバイスサポートは、このKEシリーズ(KE3000)のイーサネット通信モデルをEPICS(Linux IOC)からコントロールするものです。EPICSからの通信には、KEKBコントロールグループの小田切淳一さんが開発された、netDevツールを使用しています。本デバイスサポートは、私にとっては初めてのnetDevものなので、出来るだけシンプルに作ったつもりです。
KE3000シリーズはネットワークを通してデータ取得、チャンネル設定等全てのことが出来ます(というより、本体パネルからは値を読む以外、何も出来ない、というのが正しい)。特に、各チャンネルの設定などは、webで接続して(落とし穴はいっぱいあるにしても)簡単にしかも高速に設定できますから、このデバイスサポートでは扱わないことにします。(大体とっても面倒)。そこで、各チャンネルの値を読み込む、という機能だけを作ることにします。各モジュールは12チャンネルの入力がありますので、weveformで12チャンネル分の情報を一気に読み込むことにします。slave address 1のモジュールのch1から12個分のアナログデータを読むRTUモードでのコマンドは
意味 | binary
|
---|
スレーブアドレス | 0x01
|
ファンクションコード | 0x04
|
開始リファレンス番号(H) | 0x00
|
開始リファレンス番号(L) | 0x64
|
データ要求個数(H) | 0x00
|
データ要求個数(L) | 0x0C
|
CRC(L) | 0xB1
|
CRC(H) | 0xD0
|
となります。ファンクションコード0x04はアナログデータ要求です。ch1の開始リファレンス番号は30101ですが、コマンドではこれから30001を引いた、100が開始リファレンス番号になります。CRCは伝送フレームのエラーチェックに用いられるものです。
本デバイスサポートは、Linux PC上で動作しているEPICS R314.7+netDevで開発したものです。LinuxはRed Hat 9で、Pentium4(3GHz) CPU上で動作しています。EPICSそのものに対する説明、入門出家入道遁世については専門家に帰依するなり、コントロールグループのページをご参照なさるなりしてください。本デバイスサポートはasynドライバーは使用していないようですが、asynドライバーとは問題なく同居出来ています。
本開発で使用したpcのdirectory構造は次の通りになっています。
- PC名:pctobiyama-10
- EPICS application directory:~/epicsApp7/fblinuxApp
- netDev directory:~/epicsApp7/netDev
- device supportのsource:~/epicsApp7/netDev/chino
netDevツールをtar展開したあと、以下の設定をします。
- ~/epicsApp7/の所にあるMakefile
DIRS:=netDev
を追加
- ~/epicsAp7/netDevの所にあるMakefile
とりあえず必要のないsourceを全てコメントアウト
できあがったdevice supportをコンパイルするときは、もちろんそのための設定が必要です。詳細については後述します。
本コードはオムロンPLC用に書かれたものを参考に作りました。オムロン用コードは理化学研究所のMisaki Komiyamaさまにより書かれたものです。以下、紹介するのは
5.1 データ構造体の定義
データベースのINPフィールドから拾う情報(slave addressとかコマンド、スタートアドレスなど)を格納する場所を用意します。
typedef struct
{
int unit;
int type;
int chan;
int bit;
int width;
int slvA;
int cmd;
int start;
int ndata;
char *lopt;
} CHINO_LOG;
オリジナルにあったものの内、明らかに要らないものは省略していますが、いるか要らないか分からないものはしつこく残っています。
5.2 プロトコルの決定
TCPかUDPかを決めるのに使うfunctionのようです。TCPに決めうちしてあります。これを忘れると、UDPモードでつなごうとしてつながらず、唸ることになります。
int chinoLogUseTcp = 1;
LOCAL int chino_get_protocol(void)
{
if (chinoLogUseTcp)
{
return MPF_TCP;
}
return MPF_UDP;
}
5.3 INPフィールドから情報を拾うfunction
init_waveform_recordがchino_parse_linkを使い、データベースのINPフィールドに書いてある情報を読み出します。INPフィールドの構造は
field(INP,"@$(ip)(11111):0x01#0x04:100")
の様になっていて
@IPアドレス(ポート番号):スレーブアドレス#ファンクションコード:開始リファレンス番号
の構造になっています。chino_parse_linkはまずparseLinkPlcCommonを使ってINPフィールドをpeer_addres、unit、type, address, optitonに分けます。これらは基本的にcharなので、sscanfを使って数字に変換する必要があります。具体的には
if (sscanf(unit,"%x",&d->slvA) !=1)
{
errlogPrintf("devChinoLog :cannot get slave address\n");
}
の様にしていきます。
5.4 送信データを作るfunction
LOCAL long chino_config_command(
uint8_t *buf, /* driver buf addr */
int *len, /* driver buf size */
void *bptr, /* record buf addr */
int ftvl, /* record field type */
int ndata, /* n to be transferred */
int *option, /* direction etc. */
CHINO_LOG *d,
int sid
)
{
int nwrite;
int n;
int resCRC;
buf[ 0] = d->slvA; /* slave address */
buf[ 1] = d->cmd; /* command */
buf[ 2] = d->start>>8; /* start address (H)*/
buf[ 3] = d->start; /* start address (L)*/
buf[ 4] = 0x00; /* number (H) */
buf[ 5] = 0x18; /* number (L) 0x0c */
resCRC = calCRC(buf,6);
buf[ 6] = resCRC>>8; /* crc (H) */
buf[ 7] = resCRC ; /* crc (L) */
*len = CHINO_CMND_LENGTH;
return 0;
}
データロガーに送るコマンドを用意します。ここでは、bufにコマンドを並べるだけで、実際にはread_waveform内のnetDevReadWriteXxで読み書きしてくれます。CRCは
LOCAL unsigned short calCRC(unsigned char cd[], int nmax)
{
int i,j;
unsigned short iCRC;
unsigned short iCY,iP;
unsigned char iC1,iC2;
iCRC = 0xffff;
for (i=0;i<nmax;i++)
{
iCRC = iCRC ^ cd[i];
for (j=1; j<=8; j++)
{
iCY = iCRC & 0x1;
if ((iCRC & 0x8000) == 0x8000)
{
iP = 0x4000;
iCRC = iCRC & 0x7fff;
}
else
{
iP = 0x0;
}
iCRC = iCRC>>1;
iCRC = iCRC | iP;
if (iCY == 1){
iCRC = iCRC ^ 0xa001;
}
}
}
if ((iCRC & 0x8000) == 0x8000)
{
iP = 0x80;
iCRC = iCRC & 0x7fff;
}
else
{
iP = 0;
}
iC1 = iCRC & 0xff;
iC2 = ((iCRC & 0x7f00) >>8) | iP;
return iC1*256 + iC2;
}
で計算します。このコードはチノーのサンプルプログラム(べーしっく)をそのまま書き換えただけです(つまり、中身についてはほとんど理解していないということ)。
5.5 読み取ったデータを処理するfunction
LOCAL long chino_parse_response(
uint8_t *buf, /* driver buf addr */
int *len, /* driver buf size */
void *bptr, /* record buf addr */
int ftvl, /* record field type */
int ndata, /* n to be transferred */
int *option, /* direction etc. */
CHINO_LOG *d,
int sid
)
{
int i;
int ret;
float temp[1000],*rawVal,*ptemp;
rawVal = bptr;
if (isRead(*option))
{
for (i=0;i<12;i++)
{
ret = TwoRawToVal(buf,CHINO_DATA_OFFSET+i*4,&temp[i]);
if (ret != 0){
errlogPrintf("devGhinoLog Emergency code for %d =%x\n",i,ret);
}
}
}
ndata = 12;
ptemp = temp;
i= 12;
while (i--)
{
*rawVal++ = (float) *ptemp ++;
}
ret = 0;
return ret;
}
データロガーから送られたデータはbufの中に入っています。これから必要なデータを取り出し、waveformの配列のポインタbtprに送ります。データロガーから送られるデータは
意味 | binary
|
---|
スレーブアドレス | 0x01
|
ファンクションコード | 0x04
|
データ数 | 0x18
|
Ch1データ(H) | 0x01
|
Ch1データ(L) | 0x1D
|
Ch1データ情報(H) | 0x00
|
Ch1データ情報(L) | 0x01
|
chデータ | 0xXX
|
0xXX
|
0xXX
|
0xXX
|
CRC(L) | 0xB3
|
CRC(H) | 0xDF
|
の形をしています。各チャンネルはじめの2バイトが数字を、後ろの2バイトが小数点、ステータス情報を含んでいます。小数点・ステータス情報の形は
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
|
---|
A/D | I/O | U/F | N/A | EV4 | EV3 | EV2 | EV1 | ERR | BURN | OF | UF | DP3 | DP2 | DP1 | DP0
|
で、それぞれ
- A/D:アナログなら0、デジタルなら1
- I/O:出力なら0、入力なら1
- U/F:バイポーラなら0、ユニポーラなら1
- EV1〜4:各event状態、1で発生
- ERR:入出力スティック状態、1で異常
- BURN:センサ断線で1
- OF:オーバーフローエラー、1で発生
- UF:アンダーフローエラー、1で発生
- DP3〜DP0:小数点位置、0で×1、1で1/10、2で1/100、3で1/1000、4で1/10000
となります。このデバイスサポートではまともなエラー処理は行っていませんので、小数点位置以外の情報(CRCも含めて)は無視します。このような4バイトのデータから1つのデータを作るfunctionとして
LOCAL int TwoRawToVal(unsigned char bu[],int offset, float *res )
{
float f1;
if ((bu[offset+2] & 0x20) == 0x20) {
f1 = bu[offset]*256 + bu[offset+1];
}
else if ((bu[offset] & 0x80) == 0x80){
f1 = (bu[offset]-255)*256 +(bu[offset+1]-255);
}
else{
f1 = bu[offset]*256+bu[offset+1];
}
switch (bu[offset+3] & 0x0f)
{
case 0:
*res = f1;
break;
case 1:
*res = f1*0.1;
break;
case 2:
*res = f1*0.01;
break;
case 3:
*res = f1*0.001;
break;
case 4:
*res = f1*0.0001;
break;
default:
return -1;
}
return (bu[offset+3] & 0xf0);
}
をつかいます。エラー処理の申し訳のため、エラー以下の情報は一応returnとして戻します。
5.6 waveform処理に関して
devWaveformChinoLog.cはオリジナルよりfunction名を変更する以外、大きな変更点はありません。但し、parse_waveform_response内でchino_parse_responseの戻りに応じて色々処理をしていた場所については、「エラー処理は省略する」という流儀に応じて簡略化しています。
5.7 コンパイル
まず、~/epicsApp7/netDev/ディレクトリにあるMakefileに
SRC_DIRS += $(NETDEV)/chino
DBD += chino.dbd
netDev_SRCS += devChinoLog.c
を追加します。ここでmakeをするとO.linux-x86にバイナリコードが、また~/epicsApp7/dbdの中にchino.dbdとcore.dbdが出来ているはずです。
次に、アプリケーションディレクトリのソースのところ(~/epicsApp7/fblinuxApp/src)にあるMakefileにdbdとライブラリを追加します。
# fblinux.dbd will be created and installed
DBD += fblinux.dbd
# fblinux.dbd will be made up from these files:
#fblinux_DBD += base.dbd
#DBD += devB4phase.dbd
#fblinux_DBD += xxxSupport.dbd
#fblinux_DBD += dbSubExample.dbd
fblinux_DBD += core.dbd
fblinux_DBD += chino.dbd
# The following adds support from base/src/vxWorks
fblinux_OBJS_vxWorks += $(EPICS_BASE_BIN)/vxComLibrary
fblinux_LIBS += FBsupport
fblinux_LIBS += netDev
また、dbdファイル(ここではfblinuxInclude.dbd)にdbdファイルを追加します。
include "base.dbd"
include "devB4phase.dbd"
include "drvAsynSerialPort.dbd"
include "drvAsynIPPort.dbd"
include "core.dbd"
include "chino.dbd"
これで一番上でmakeすればデータベースを除いて一応完成です。
アプリケーションディレクトリーのデータベースの所にデータベースを作ります。今のところ1スロットしかないので、データベースもwaveform1個です。
record(waveform,"$(USER):SLOT1") {
field(DESC,"waveform record")
field(SCAN,"5 second")
field(PINI,"NO")
field(PHAS,"0")
field(EVNT,"0")
field(DTYP,"ChinoLog")
field(DISV,"1")
field(SDIS,"0.000000000000000e+00")
field(DISS,"NO_ALARM")
field(PRIO,"LOW")
field(FLNK,"0.0")
field(RARM,"0")
field(PREC,"3")
field(INP,"@$(ip)(11111):0x01#0x04:100")
field(EGU,"V")
field(HOPR,"10.0")
field(LOPR,"-10.0")
field(NELM,"12")
field(FTVL,"FLOAT")
field(SIOL,"0.0000000e+00")
field(SIML,"0.0000000e+00")
field(SIMS,"NO_ALARM")
}
忘れずにここにあるMakefileも
#DB += dbExample2.db
#DB += dbSubExample.db
DB += FB_TB4PHASE.db
DB += FB_CHINOEX.db
のように変更して、新しいデータベースを追加しておきます。ここ、そして上でmakeして完成です。
#!../../bin/linux-x86/fblinux
## You may have to change fblinux to something else
## everywhere it appears in this file
< envPaths
cd ${TOP}
## Register all support components
dbLoadDatabase("dbd/fblinux.dbd")
fblinux_registerRecordDeviceDriver(pdbbase)
## Load record instances
dbLoadRecords("db/FB_CHINOEX.db","USER=FBTEST,ip=172.19.xxx.xxx")
## Set this to see messages from mySub
#var mySubDebug 1
cd ${TOP}/iocBoot/${IOC}
iocInit()
## Start any sequence programs
#seq sncExample,"user=tobiyamaHost"
#seq &PHASE_CNTL
#dbpf "FB_TB4:PHASE:INPUT:AUTO","1"
のような形になると思います。Asynドライバーと違い、IPはデータベースで直接指定することになります。
netDevツールを使い、チノーデータロガーデータをEPICSから読み取るデバイスサポートの製作を紹介しました。本デバイスサポートは、エラー処理を完全にさぼっており、またセンサーを交換したときのデータフォーマット変化へも完全には対応しておりませんので、汎用的にはもう一息工夫が要りそうです。近いうちにnetDevの大幅変更が予定されているようなので、その機会にもうちょっと変更をしても良いかもしれないとも思わないではありません(要はこれ以上面倒なことは遠慮したいのが本音)。
netDevのインストール、使い方等の説明、本コードの動作に関する相談などKEKBコントロールグループの小田切淳一さんに大変お世話になりました。感謝します。
mailto: makoto.tobiyama@kek.jp
Last update: 22/Jul/2005