[KEKB Bunch Feedback Group]

チノーネットワークロガーKEシリーズ用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ツールを使用しています。本デバイスサポートは、私にとっては初めての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.7+netDevで開発したものです。LinuxはRed Hat 9で、Pentium4(3GHz) CPU上で動作しています。EPICSそのものに対する説明、入門出家入道遁世については専門家に帰依するなり、コントロールグループのページをご参照なさるなりしてください。本デバイスサポートはasynドライバーは使用していないようですが、asynドライバーとは問題なく同居出来ています。
本開発で使用したpcのdirectory構造は次の通りになっています。

4.netDevツールのインストール

netDevツールをtar展開したあと、以下の設定をします。 できあがったdevice supportをコンパイルするときは、もちろんそのための設定が必要です。詳細については後述します。

5.コードの概要

本コードはオムロン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バイトが小数点、ステータス情報を含んでいます。小数点・ステータス情報の形は
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処理に関して

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すればデータベースを除いて一応完成です。

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

アプリケーションディレクトリーのデータベースの所にデータベースを作ります。今のところ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して完成です。

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

#!../../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はデータベースで直接指定することになります。

8.おわりに

netDevツールを使い、チノーデータロガーデータをEPICSから読み取るデバイスサポートの製作を紹介しました。本デバイスサポートは、エラー処理を完全にさぼっており、またセンサーを交換したときのデータフォーマット変化へも完全には対応しておりませんので、汎用的にはもう一息工夫が要りそうです。近いうちにnetDevの大幅変更が予定されているようなので、その機会にもうちょっと変更をしても良いかもしれないとも思わないではありません(要はこれ以上面倒なことは遠慮したいのが本音)。
netDevのインストール、使い方等の説明、本コードの動作に関する相談などKEKBコントロールグループの小田切淳一さんに大変お世話になりました。感謝します。
mailto: makoto.tobiyama@kek.jp
Last update: 22/Jul/2005