[KEKB Bunch Feedback Group]

チノーネットワークロガーKE3000用EPICS device supportの製作 改版(Japanese)


by とびやま まこと(Makoto Tobiyama)/KEKB ビームモニターグループ

警告
以下の記述に関しては、意図する、しないに関わらず極めて多くの誤り、誤解が含まれていると思われますので、決して信用してはいけません。これを信じて起きた損害に関しては、当方は一切責任を持ちません。


If you need contact with the author, please E-mail makoto.tobiyama@kek.jp.
目次

1.はじめに

チノー株式会社のネットワークロガーKEシリーズは、高速・多点のデータ集録に適したネットワーク対応 のロガーです。各機能はユニット化されており、必要なユニットを組み合わせることで用途に応じたシステム を構築出来ます。具体的なシステムに関する情報は、 メーカーのページをご参照ください。
本デバイスサポートは、このKEシリーズ(KE3000)のイーサネット通信モデルをEPICS(Linux IOC) からコントロールするものです。全スロットデータを一度に読み込むことで、 旧バージョンと比べ、 データ取得をより確実にしています(微妙な表現:お察し下さい)。 EPICSからの通信には、KEKBコントロールグループの 小田切淳一さんが開発された、netDevツールを使用しています。

2.サポートする機能

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は伝送フレームのエラーチェックに用いられるものです。

3.EPICS環境

本デバイスサポートは、Linux PC上で動作しているEPICS R314.12.1(compactSubarray拡張付き)+netDev-1.0.2で開発したものです。 EPICSそのものに対する説明、入門出家入道遁世については専門家に帰依するなり、コントロールグループのページをご参照なさる なりしてください。
netDev本体、及びこのデバイスサポートは、epicsのextensionsディレクトリにinstallします。具体的には をしています。

4.ユーザーディレクトリでの設定

ユーザー側の環境例として、sourceディレクトリが とします。 ユーザー側では、srcのMakefileで
fblinux_DBD += netDev.dbd

fblinux_LIBS += netDev
の設定、make clean, makeが必要です。

5.コードの概要

本コードは旧バージョンを改造したものです。

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バイトが小数点、ステータス情報を含んでいます。小数点・ステータス情報の形は
1514131211109876543210
A/DI/OU/FN/AEV4EV3EV2EV1ERRBURNOFUFDP3DP2DP1DP0
で、それぞれ となります。このデバイスサポートではまともなエラー処理は行っていませんので、 小数点位置以外の情報(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が必要です。

6.EPICSデータベースサンプル

アプリケーションディレクトリーのデータベースの所にデータベースを作ります。例として、電磁石 温度を測定している、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ですと という呪いがかかります。正直、お馬鹿すぎる仕様と思います。
上記のデータベースをいちいち手でいれるのはさすがに大変なので、実際は excelファイルから定型データベースへの変換をするコードを使っています。

7.スタートアップファイルサンプル

以下に例を示します。
#!../../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

のような形になります。

8.おわりに

netDevツールを使い、チノーデータロガーデータをEPICSから読み取るデバイスサポート改良版を紹介しました。
mailto: makoto.tobiyama@kek.jp
Last update: 24/Oct/2013