目次
チノー株式会社のネットワークロガーKEシリーズは、高速・多点のデータ集録に適したネットワーク対応
のロガーです。各機能はユニット化されており、必要なユニットを組み合わせることで用途に応じたシステム
を構築出来ます。具体的なシステムに関する情報は、
メーカーのページをご参照ください。
本デバイスサポートは、このKEシリーズ(KE3000)のイーサネット通信モデルをEPICS(Linux IOC)
からコントロールするものです。全スロットデータを一度に読み込むことで、
旧バージョンと比べ、
データ取得をより確実にしています(微妙な表現:お察し下さい)。
EPICSからの通信には、KEKBコントロールグループの
小田切淳一さんが開発された、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.12.1(compactSubarray拡張付き)+netDev-1.0.2で開発したものです。
EPICSそのものに対する説明、入門出家入道遁世については専門家に帰依するなり、コントロールグループのページをご参照なさる
なりしてください。
netDev本体、及びこのデバイスサポートは、epicsのextensionsディレクトリにinstallします。具体的には
- /local/R314.12.1-CSA/extensions/src/netDev/netDev-1.0.2にnetDevを展開、currentにシンボリックリンク
- confgureのRELEASE中、EPICS_BASEを/local/R314.12.1-CSA/baseに設定
- /local/R314.12.1-CSA/extensions/configure/RELEASE中のEPICS_BASEとEPICS_EXTENSIONSを設定
をしています。
ユーザー側の環境例として、sourceディレクトリが
とします。
ユーザー側では、srcのMakefileで
fblinux_DBD += netDev.dbd
fblinux_LIBS += netDev
の設定、make clean, makeが必要です。
本コードは旧バージョンを改造したものです。
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;
int data_trans;
char *lopt;
} CHINOL_LOG;
旧バージョンに比べ、データ転送数のフィールドが追加されています。
5.2 プロトコルの決定
TCPかUDPかを決めるのに使うfunctionのようです。TCPに決めうちしてあります。これを忘れると、UDPモードでつなごうとしてつながらず、唸ることになります。
int chinoLogLUseTcp = 1;
LOCAL int chino_get_protocol(void)
{
if (chinoLogLUseTcp)
{
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&60")
の様になっていて
@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. */
CHINOL_LOG *d,
int sid
)
{ int nwrite;
int n;
int resCRC;
LOGMSG("devChinoLogL: chino_config_command(0x%08x,%d,0x%08x,%d,%d,%d,0x%08x)\n",
buf,*len,bptr,ftvl,ndata,*option,d,0,0);
n = ndata;
nwrite = isWrite(*option) ? (d->width)*n : 0;
if (*len < CHINOL_CMND_LENGTH + nwrite)
{
errlogPrintf("devChinoLogL: buffer is running short\n");
return ERROR;
}
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] = (d->data_trans)*2; /* number (L) 0x0c */
resCRC = calCRC(buf,6);
buf[ 6] = resCRC>>8; /* crc (H) */
buf[ 7] = resCRC ; /* crc (L) */
/* errlogPrintf("devChinoLogL: command = %x %x %x %x %x %x %x %x\n",buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7]); */
*len = CHINOL_CMND_LENGTH;
/* if (isWrite(*option))
{
if (fromRecordVal(
&buf[CHINOL_CMND_LENGTH],
d->width,
bptr,
d->noff,
ftvl,
n,
CHINOL_NEEDS_SWAP
))
{
errlogPrintf("devChinoLogL: illeagal command\n");
return ERROR;
}
} */
/*
nread = isRead(*option)? (d->width)*n:0;
return (CHINOL_DATA_OFFSET + nread);
*/
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. */
CHINOL_LOG *d,
int sid
)
{ int i;
int ret;
float temp[1000],*rawVal,*ptemp;
rawVal = bptr;
LOGMSG("devChinoLogL: chino_parse_response(0x%08x,%d,0x%08x,%d,%d,%d,0x%08x)\n",
buf,len,bptr,ftvl,ndata,*option,d,0,0);
/* for (i=0;i<12;i++)
{
errlogPrintf("devChinoLogL buffer %d = %x %x %x %x\n",i*4,buf[CHINOL_DATA_OFFSET+i*4],buf[CHINOL_DATA_OFFSET+i*4+1],buf[CHINOL_DATA_OFFSET+i*4+2],buf[CHINOL_DATA_OFFSET+i*4+3]);
} */
if (isRead(*option))
{
for (i=0;i<(d->data_trans);i++)
{
ret = TwoRawToVal(buf,CHINOL_DATA_OFFSET+i*4,&temp[i]);
if (ret != 0){
switch (ret & 0xf0){
case 0x10:
temp[i]=-10.0;
break;
case 0x20:
temp[i]=10.0;
break;
case 0x30:
temp[i]= -1000.0;
break;
default:
temp[i] = -1000.0;
/* errlogPrintf("devGhinoLog Emergency code for %d =%x\n",i,ret); */
}
}
}
}
ndata = d->data_trans;
ptemp = temp;
i= ndata;
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処理に関して
devWaveformChinoLogL.cはオリジナルよりfunction名を変更する以外、大きな変更点はありません。
但し、parse_waveform_response内でchino_parse_responseの戻りに応じて色々処理をしていた
場所については、「エラー処理は省略する」という流儀に応じて簡略化しています。
5.7 コンパイル
extensions/src/netDev/current/srcディレクトリのMakefileに
netDev_SRCS += devChinoLogL.c
を追加、make clean; makeを行います。また、ユーザーディレクトリでも、make clean; makeが必要です。
アプリケーションディレクトリーのデータベースの所にデータベースを作ります。例として、電磁石
温度を測定している、2スロット入ったモジュール用のデータベースを示します。全体は
こちらのリンクをご参照ください。
データをまず、NELM 60のwaveformでとります。scanは5秒にしています。
record(waveform,"$(USER)_MG:$(PLACE):SLOTA") {
field(DESC,"waveform record")
field(SCAN,"5 second")
field(PINI,"NO")
field(PHAS,"0")
field(EVNT,"0")
field(DTYP,"ChinoLogL")
field(DISV,"1")
field(SDIS,"0.000000000000000e+00")
field(DISS,"NO_ALARM")
field(PRIO,"LOW")
field(FLNK,"$(USER)_MG:$(PLACE):SLOTA:FAN0.VAL")
field(RARM,"0")
field(PREC,"3")
field(INP,"@$(ip)(11111):0x01#0x04:100&24")
field(EGU,"deg")
field(HOPR,"550.0")
field(LOPR,"-200.0")
field(NELM,"60")
field(FTVL,"FLOAT")
field(SIOL,"0.0000000e+00")
field(SIML,"0.0000000e+00")
field(SIMS,"NO_ALARM") }
この場合、スロットは2つ入っているので、12×2=24個のデータを取ります。この
eventはfantout (fan0からfan1およびfan2、それからfan11-fan16、fan21-fan26へ、
さらにそれから個別のcompactSubarrayレコードへeventを分配します。
なお、compactSubarrayがない通常のEPICS環境ではsubArrayレコードを使うことに
なると思われますが、subArrayですと
- 各subArrayレコードが親と同じ大きさの配列を抱えている。このため
例えば60chだと60×60=3600にデータ量が無駄に増える
- さらに、subArrayはデータを渡す際にコピーをするので、時間がかかる。
60個程度のデータではまあ大したことはありませんが。
という呪いがかかります。正直、お馬鹿すぎる仕様と思います。
上記のデータベースをいちいち手でいれるのはさすがに大変なので、実際は
excelファイルから定型データベースへの変換をするコードを使っています。
以下に例を示します。
#!../../bin/linux-x86/fblinux
## You may have to change epics to something else
## everywhere it appears in this file
< envPaths
cd ${TOP}
## Register all support components
#dbLoadDatabase("../../dbd/epics.dbd",0,0)
#epics_registerRecordDeviceDriver(pdbbase)
dbLoadDatabase("dbd/fblinux.dbd",0,0)
fblinux_registerRecordDeviceDriver(pdbbase)
## Load record instances
dbLoadRecords("db/MG_CHINOLC5.db","USER=MG, PLACE=LC5, ip=172.19.xxx.xxx")
# Set this to see messages from mySub
#var mySubDebug 1
iocInit()
## Start any sequence programs
のような形になります。
netDevツールを使い、チノーデータロガーデータをEPICSから読み取るデバイスサポート改良版を紹介しました。
mailto: makoto.tobiyama@kek.jp
Last update: 24/Oct/2013