[KEKB Bunch Feedback Group]

TD-4V用EPICS Device Supportの製作(Japanese)


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

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


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

1.はじめに

EPICSでVMEのボードを制御するためには、そのボード用のデバイスサポートを用意する必要があります。売り物のVMEボードで、かつそのデバイスサポートがすでにある(誰かによって用意されている)ものなら、そのままありがたく使えば良いのですが、自分で作ったボードやほとんどの日本製のVMEボードについては、自分でデバイスサポート用意することになります。ここでは、私たちが開発したVMEボード(TD-4V)のデバイスサポートを例に、簡単なデバイスサポートの作り方を紹介します。TD-4Vについての説明は、こちらを参照して下さい。

これから紹介するTD-4V用デバイスサポートは、元々コントロールグループの秋山篤美さんが作ってくださったもので、それをもとに飛山が極一部書き換えたものです。よって、難しい質問には、飛山はとてもお答えできませんです。以下、EPICSを使う準備をし、デバイスサポートを書き、データーベースを作り、アクセスするところまで紹介します。EPICSの達人で、準備等はつまらんというお方は、こちらをクリックして、先に進んでくだされ。私は前の報告の時も、書きながら自分の物忘れのすごさにあきれていましたが、今回は全く感服するほどなにも覚えていませんでしたので、自分の備忘録代わりに簡単な事も含めて記録しておくことにします。


2.準備

では、EPICSを使う準備をしましょう。少なくともSAD計算機にアカウントはあり、「使える」ことを前提とします。そうでない場合は、どなたか達人に三顧の礼をとって教えを請うことにしましょう。

はじめに、これから作るデバイスサポート用の作業ディレクトリをつくります。大胆にも今までepics開発をやっていたところで作業しても勿論かまいませんが、なにかきわめて恐ろしいことが起きて、気が変になって「えい、全部消したれ!」とわめいた時に、悲しい事が起こるかもしれません。例としてepics.td4というディレクトリをホームディレクトリの下に作ります。

mkdir ~/epics.td4
cd ~/epics.td4

ここで、余計なことは考えずに、サブディレクトリを自動的に作ります。
cd ~/epics.td4
/usr/local/bin/setup_epics_dir

これで、いくつかの作業用ディレクトリと、いくつかのlinkが出来ます。余計なことを考えて、手で一部作ったあげく、上のスクリプトを走らせるのは最低の選択で、多くの場合後々むごい結末に見舞われるでしょう(私のことや)。多くの方は、すでにEPICSの環境変数は設定してあると思いますが、そうでない場合、~/.cshrcに(shellがcsh/tcshの時)次の2行を加え
setenv EPICS "/cont/epics/R312/epics"
source $EPICS/appl/ase/rc.epicsAppl

source .cshrcとして、環境を有効にします。

デバイスサポートは、~/epics.td4/srcディレクトリで作ることにします。

3.デバイスサポート

VMEバスの詳細について興味がある方は、以下の本が参考になると思います。 TD4-Vは標準特権データモードあるいは拡張特権データモードで動作しますが、ここでは標準特権モード用のデバイスサポートを作ります。TD4-Vの現在のコマンドは、以下の通りです。
アドレス入出力機能
0x*******0R/Wプリセット入力、設定値の表示
0x*******4R/WEnable/Disableの設定、表示
0x*******8R最終設定値、現場指示値

いずれも16ビットのデータ長(unsigned short)があります。間のアドレスが飛んでいるのは、拡張特権データモードでアクセスするときのためです。

さて、それではいよいよデバイスサポートについて説明します。まず、せっかちの人のため全ソースファイルを以下に示します。
ソースファイルを見る

ヘッダーファイル

これから使うデータ形式(bi、bo、longin、longout)に関するデータ構造は忘れずにincludeする必要があります。
#include	<longinRecord.h>
#include	<longoutRecord.h>
#include        <boRecord.h>
#include        <biRecord.h>
例えば、このほかにmbboが必要なら#include <mbboRecord.h>とすればいいわけです。

データアクセス構造

使っているアドレス構造を構造体で作ります。
struct DvmeTd4v {
	unsigned short	PreSet;
	unsigned short	Dummy0;
	unsigned short	Enable;
	unsigned short	Dummy1;
	unsigned short	Last_PreSet;
	unsigned short	Dummy2;
        unsigned short  Dummy3;
        unsigned short  Dummy4;
};
上で示した、アドレス構造と比較して下さい。なお、Last_PreSetの後ろのdummyは、複数枚カードを使用する場合、面倒が起きないように、アドレススペースを0x10毎にするために付けたものです。たとえば、一枚目のカードのアドレスが0x00200000だとすると、二枚目は0x00200010とするわけです。このアドレススペースの割り振りについては、多分今にコントロールグループからガイドラインが発表されるものとちょっとだけ期待しております。

関数のプロトタイプ

以下で出てくる関数のプロトタイプを宣言します。どうもANSIタイプでないのが気色悪いですが、まあ気にしないでくだされ。
static long init();
static long init_record();
static long read_longin();
static long write_longout();
static long init_bi_record();
static long init_bo_record();
static long read_bi();
static long write_bo();
static int  checkLink();

EPICSデータアクセスの定義

このデバイスサポートが許す、デバイスアクセス構造を定義します。例えば、
struct {
	long		number;
	DEVSUPFUN	report;
	DEVSUPFUN	init;
	DEVSUPFUN	init_record;
	DEVSUPFUN	get_ioint_info;
	DEVSUPFUN	read_longin;
	DEVSUPFUN	special_linconv;
}devLiTd4v={
	6,
	NULL,
	init,
	init_record,
	NULL,
	read_longin,
    NULL};
は、longin型のアクセス「devLiTd4v」(名前は自分で決める)を定義します。初期化はinit_recordと言う関数で、readアクションはread_longinという関数で行います。このうち、[devLiTd4V]という名前は、後でデバイスの登録の時にまた出てきます。同じように、devLoTd4v、devBoTd4v、devBiTd4vを定義します。

カードアドレス、排他処理用構造体の定義

複数枚のカードを使用する場合、カードはcard番号で区別する事になります。このため、各カードのアドレスを覚えておく場所が必要になります。また、排他処理のためのフラグを用意します。
struct ioCard {
        volatile struct DvmeTd4v     *card; /* address of this card */
        FAST_LOCK                    lock; /* semaphore */
      };

カード構造体、デバッグフラグ等

カード構造体の実体、デバッグ用のフラグを用意します。例えば、TD-4Vはどんなにがんばっても1サブラックに10枚までしか入れないと思うので、
#define CONST_NUM_LINKS 10
/* #define STATIC  */
#define DEBUG_ON

int            debug_flag = 0;
static unsigned long Base_IO;
static int     td4v_num_links;

static struct ioCard cards[CONST_NUM_LINKS];
static int           init_flag = 0;
で、カード構造体を10個まで作ります。また、デバッグが必要なとき、debug_flagを、たとえば10とかにすると、以下の各関数が動作するたびに、IOCのモニターにうるさくメッセージが出ます。init_flagは、次の初期化ルーチンを1回だけ動かすためにはじめは0にしておきます。初期化ルーチンが1回通ると、この値が1になりますので、もしもう1回初期化ルーチンが呼ばれても、初期化動作はパスするようになっています。

ベースアドレス、カード数設定

startupファイルで、1番目のTD-4Vのアドレス(ベースアドレス)、何枚TD-4Vを使うかを設定するため、 次のconfig関数を使います。
int devTd4vConfig(ncards,a24base)
int ncards;
long a24base;
{
  td4v_num_links = ncards;
  Base_IO = a24base;
  printf("VmeTd4 NumLink= %d BaseIO= %x\n",td4v_num_links,Base_IO);
  init(0);
}
例えば、TD-4Vの1枚目のベースアドレスが0x00200000で、2枚使用する時は、スタートアップファイルのiocInitの前に
#Initialize TD4V
devTd4vConfig(2,0x200000)

# start up EPICS tasks
# cd "/users/epics/e312"
iocInit "resource.def"
という様に、入れることになります。2枚目のTD-4Vのベースアドレスは0x00200010であることに注意して下さい。(データベースが間違っていなければ、別段2枚目をパスして3枚目を入れてもかまわない)

初期化関数

一つのIOCの下で何枚かTD-4Vを使うとき、EPICSデータベース上では、それぞれのTD-4Vをカード番号(0から?まで)で区別することになります。例えば、ベースアドレス0x00200000のTD-4Vがカード番号0、0x00200010のTD-4Vがカード番号1といった具合です。このカード番号と実際のアドレスとの対応をはじめに(1回だけ)付けておく必要があります。VxWorksでは、標準アドレススペースを使うものは、0xfc******のアドレスに配置されます。これを決め打ちしても普通は良いのですが、CPUが変わったりしたとき、この割り振りを変えられる可能性もありますので、VxWorksの関数でアサインしておく方が安全です。
static long init(after)
int after;
{
  int                          cardNum, chanNum;
  unsigned char                probeVal[2];
  volatile  struct DvmeTd4v    *p;

  if (init_flag != 0 ) 
   return(OK);

  init_flag = 1;

  if (sysBusToLocalAdrs(VME_AM_STD_SUP_DATA,(char *)Base_IO,(char **)&p) == ERROR)
	{
		printf("VmeTd4v: cannot find standard address space\n");
		return(ERROR);
	}
下に続く...
このsysBusToLocalAdrsで、Base_IOのアドレスに対して、VME_AM_STD_SUP_DATA(標準特権データアクセスでAMコードは0x3d)アクセスをするためのアドレススペースをpにアサインします。例えば、Base_IOが0x00200000だとすると、pのアドレスは0xfc200000になるはずです。他のアクセスモードに関しては、/proj/epics/e312/appl/vmebf1/vw/hにあるvme.hを参照して下さい。
定義名AMコード機能
VME_AM_STD_SUP_ASCENDING0x3f標準特権ブロック転送
VME_AM_STD_SUP_PGM0x3e標準特権プログラムアクセス
VME_AM_STD_SUP_DATA0x3d標準特権データ・アクセス
VME_AM_STD_USR_ASCENDING0x3b標準非特権ブロック転送
VME_AM_STD_USR_PGM0x3a標準非特権プログラム・アクセス
VME_AM_STD_USR_DATA0x39標準非特権データ・アクセス

VME_AM_SUP_SHORT_IO0x2dショート特権アクセス
VME_AM_USR_SHORT_IO0x29ショート非特権アクセス

VME_AM_EXT_SUP_ASCENDING0x0f拡張特権ブロック転送
VME_AM_EXT_SUP_PGM0x0e拡張特権プログラム・アクセス
VME_AM_EXT_SUP_DATA0x0d拡張特権データ・アクセス
VME_AM_EXT_USR_ASCENDING0x0b拡張非特権ブロック転送
VME_AM_EXT_USR_PGM0x0a拡張非特権プログラム・アクセス
VME_AM_EXT_USR_DATA0x09拡張非特権データ・アクセス

前から続く...

  for (cardNum=0; cardNum< td4v_num_links; cardNum++)
   {
     if (vxMemProbe((char*) &(p->PreSet), READ, 2, &probeVal[0])< OK)
       {
	 if (debug_flag >0 ) 
         printf("No TD4V with cardNum= %d\n probe= %x\n",cardNum,p);
	 cards[cardNum].card = NULL;
       }
     else
       {
	 if (debug_flag >0)
         printf("Found TD4V with cardNum= %d\n address= %x\n",cardNum,p);
	 cards[cardNum].card = p;  /* Remember address of the board */
	 FASTLOCKINIT(&(cards[cardNum].lock));
	 FASTUNLOCK(&(cards[cardNum].lock)); /* Init the board lock */
       }
     p++;
   }
  return(OK);
}
次に、あり得るアドレス内のTD-4Vを探して、そのアドレスをアサインします。まず、vxMemProbeでTD-4Vに対してREADアクションをかけてみます。もしも、そのアドレスにTD-4Vがないなら、Bus errorになりますから、cards構造体の該当カード番号のところをNULLにしておきます。TD-4Vがあって、ちゃんと値が読めれば(つまりBUS errorにならないなら)、cards構造体の該当カード番号のところに、そのアドレス(p)を入れておき、lockフラグを初期化します。これをpをインクリメントしながら、TD-4Vがあり得る数まで繰り返します。これで、カード番号とVMEアドレスとの対応が出来ました。

longinの初期化関数

devLiTd4v構造体の中で、init_recordという部分がありましたが、この内容を記述します。今回はなにもしませんから、中身は空です。
static long init_record(plongin)
struct longinRecord	*plongin;
{
  return(0);
}

指定されたカード番号のTD-4Vがあるか調べる関数

データベースで指定したカード番号のTD-4Vが本当にあるかどうか調べる関数です。動作はごらんになればおわかりと思います。
/**************************************************************************
 *
 * Make sure card number is valid
 *
 **************************************************************************/
static int checkLink(cardN)
short   cardN;
{
  if (cardN >= td4v_num_links)
    return(ERROR);
  if (cards[cardN].card == NULL)
    {
      printf("No TD4V with this number = %d\n",cardN);
      return(ERROR);
    }

  if (debug_flag >0)
    printf("Yes you have TD4V with card No= %d\n",cardN);
  return(OK);
}

longinの具体的動作を記述する関数--read_longin(plongin)

longinレコードを通してTD-4Vのプリセット値、最終設定値を読みとる関数を作ります。
static long read_longin(plongin)
struct longinRecord	*plongin;
{
  short cardN;

  if (debug_flag >0)
    printf("read_longin called...\n");

  cardN = plongin->inp.value.vmeio.card;
  if (debug_flag >0)
    printf("read_longin called with card number of %d\n",cardN);

  if (checkLink(cardN) == ERROR)
    return(ERROR);

下に続きます...

まず、これからアクセスしたいTD-4Vのカード番号をデータベースから読みとります。場所はplongin->inp.value.vmeio.cardにあります。このcard numberがvalidかどうか上のcheckLinkで調べます。存在しないvmeアドレスに無理矢理アクセスすると、きっとaccess violationでshellが落っこちます。

前から続く...

		switch(plongin->inp.value.vmeio.signal){
			case 0:
		               plongin->val = cards[cardN].card->PreSet;
                               if (debug_flag >0)
                                printf("read_longin signal 0 Preset %d\n",
                                        plongin->val);

				break;
			case 1:
				plongin->val = cards[cardN].card->Last_PreSet;
                                if (debug_flag >0)
                                printf("read_longin signal 1 Last Preset %d\n",
                                        plongin->val);
				break;
			default:
				return(-1);
		}
        return(CONVERT);
}
longinで読む2つのデータは、signal fieldで区別します。signalが0の時はpreset値を、signalが1の時は最終preset値を読みます。

longoutの動作--write_longout

TD-4Vのプリセット値を設定します。構造的には、read_longinと同じです。
static long write_longout(plongout)
struct longoutRecord	*plongout;
{

  short cardN;
  
  cardN = plongout->out.value.vmeio.card;

  if (debug_flag >0) 
   printf("write_longout called with card %d\n",cardN);

  if (checkLink(cardN) == ERROR)
    return(ERROR);

  FASTLOCK(&(cards[cardN].lock));
  if (debug_flag >0)
   printf("card locked...\n");
  cards[cardN].card->PreSet = plongout->val;
  if (debug_flag >0)
   printf("write complete \n");
  FASTUNLOCK(&(cards[cardN].lock));
  if (debug_flag >0)
   printf("unlock card ...\n");

        return(CONVERT);
}
read_longinと同じように、カード番号でTD-4Vを管理します。書く先はreadの時と違って1つしかありませんので、signalフィールドでの管理はありません。readの時とちがうのは、データの書き込みをデバイスに対して行うときに、FASTLOCKでデバイスをロックし、その後FASTUNLOCKでロックを解除しているところです。

BI/BOへのアクセス

longin、lognoutと同じようにBI/BOへのアクセスも記述します。TD-4VではLSBの1ビットしかつかいませんので、マスクも単純なもので、
static long init_bo_record(pbo)
struct boRecord *pbo;
{

        pbo->mask = 1;
		return(0);
}
と用意しておいて、例えば
static long
write_bo(pbo)
struct boRecord *pbo;
{

  short cardN;

  cardN = pbo->out.value.vmeio.card;
  if (checkLink(cardN) == ERROR)
    {
      printf("Error--- No TD4V for card %d\n",cardN);
      return(ERROR);
    }

  FASTLOCK(&(cards[cardN].lock));
  cards[cardN].card->Enable = pbo->rval & pbo->mask;
  FASTUNLOCK(&(cards[cardN].lock));
	return(0);
}
と実際書き込んだり、読んだりするときに&でlogical andをとれば良いのです。

4.デバイスサポートのコンパイル、登録

デバイスサポートをコンパイルします。Makefileを用意し、
make devTd4v
cp devTd4v.o ../objfrc40/.
とします。エラーが出たり、警告が出たら無視せずに直すこと。また、ここでエラーが出なかったとしても決して安心してはいけません。相手はなにせCなのですからきっと思いもよらない罠を仕掛けているにちがいありません。謙虚な人は、はじめはデバッグ命令をいっぱい付けておくのが良いでしょう(それでも滅びが待っている場合が多いのですが)。

次に、デバイスサポートの登録を行います。~/epics.td4/cat_asciiディレクトリに行き、devSup.asciiファイルを編集します。

#define INClinkh
#include <link.h>

"longin"        VME_IO          "devLiTd4v"             "TD-4V"
"longout"       VME_IO          "devLoTd4v"             "TD-4V"
"bo"            VME_IO          "devBoTd4v"             "TD-4V"
"bi"            VME_IO          "devBiTd4v"             "TD-4V"

もし他にGP-IBなどのデバイスがあれば、ここにもっと続く

次にepics rootディレクトリ(この場合~/epics.td4)へ上がり、makesdrを行います。ここでうまくいかない場合は、epicsディレクトリの構造が正しく出来ているかチェックして下さい。なにが正しいか分からない人は、名人上手にお伺いをたてて下さい。

5.データベースの構築

データベースを作ります。ディレクトリを~/epics.td4/dbにうつり、作業を始めます。今回はXDCTを使うことにします。(というか、取り敢えずこれしか覚えていないです。)
XDCT $DISPLAY &
とします。メニューでCreate a Process Databaseを選び、新しくデーベースを作ります。XDCTはカーソルで動き、終了はctrl-Eです。今回は、td4v01という名前のデータベースとします。このあと、process variableを作っていきます。今、2台のTD-4V(カード番号0と1)を使うとすると以下のようなデータベースを作ることになります。(この名前はとても良くない例です。本当は、コントロールグループがお決めになった正しい命名規則に従う必要があります。そうでないと、あちこちで名前がぶつかるという不幸な結果に見舞われるでしょう)
データタイプ名前Device TypeCardSignalHOPRZero NameOne Name
bitd4v01:td4statusTD-4V0EnableDisable
botd4v01:td4enableTD-4V0EnableDisable
longintd4v01:td4lastpresetTD-4V0165535
longintd4v01:td4presetTD-4V0065535
longouttd4v01:td4outTD-4V065535

bitd4v02:td4statusTD-4V1EnableDisable
botd4v02:td4enableTD-4V1EnableDisable
longintd4v02:td4lastpresetTD-4V1165535
longintd4v02:td4presetTD-4V1065535
longouttd4v02:td4outTD-4V165535

inputのスキャンタイプは、例えば1秒とか10秒とかにします。Device typeのselectionでTD-4Vがでてこない場合は、makesdrがうまくいっていないので、あきらめてもう一度前からやり直すしかありません。初期化、link等を付けたいひとは、どうぞお付け下さい。難しいことをする場合は、capfastとか使った方がわかりやすいかもしれませんね。

データーベースが出来たら、これをコンバートします。

/cont/epics/local/etc/dct2db td4v01
とかすると、dbファイルができます。

6.スタートアップファイルの作成

スタートアップファイル(初期値はstartup.frc40)を編集して、データベースの読み込みと、デバイスサポートの登録、devTd4vの初期化等を行うように設定します。次のリンクを参考にして下さい。

スタートアップファイル(startup.vme)


7.VME計算機(IOC)へのロード

TD-4Vが入っているVMEサブラックのVMECPU(IOC)をリブートし、スタートアップファイルをロードします。リブートは、telnetで入ってrebootしても良いですし、直接IOCをリブートしても良いです。なお、すでに何かのstartupを実行するようになっていたら、bootChangeで変更する必要があります。
telnet vmebfどれだか
< /users/tobiyama/epics.td4/startup.vme
とすれば、運が良ければ落っこちずにプログラムがロードされます。ここでこけた人は、残念ながらなにかが間違っていますので、間違っている箇所をなおして下され。こけなかった人も、これでokというにはまだ早いです。実際にデータベースを介してTD-4Vを動かしてみて、大丈夫であることを確認して下さい。

注意:長い時間IOCに電気を入れていないと、ipアドレスとかブーティングデバイスを記憶しているSRAMのバックアップ電池が切れてきれいさっぱり白紙になってしまっていることがあります。書き換え可能ROMデバイスが簡単に使えるこのご時世に(TD-4Vだって持っている)今時電池バックアップとはまことに感服つかまつり恐れ入りますが、こうなってしまってはどうにもなりません。前面のRS-232Cポートに腐れVT100端末をつないで、設定をやり直す必要があります。これがまたコネクタが異常な格好をしているので、コントロールの山本さんに頼るしか助かる道はありません。なお、この状態から電気を入れておけば電池が復活するのかどうかについては、私は知りません。たとえ二次電池としても、放電させてしまった罪はきわめて重いと思われます。ごめんなさい許して下さい。


8.データベースのアクセステスト

unixの上からcaget、caputコマンドを使い、動作を確認しましょう。例えば、プリセットの値を変えたいときは、
caput td4v01:td4out 5
とかすると、1番目のTD-4Vのプリセット値が変わるはずです。ここでaccess faultで落っこちたりすると、やはり何かが間違っています。値のモニターは
caput td4v01:td4preset.PROC 1
caget td4v01:td4preset
とすると、現在の値を読んできます。1行目は、データベースの更新を強制的にさせていることになります。ここまで出来てうまくいっているようでしたら、ご自分の好きな、medmなり、ぱいそんなり、sadでコントロールプログラムを作ればよいでしょう。例として、medmで作ったパネルを示します。

9.最後に

TD-4Vのデバイスサポートを例に、自分で作ったVMEボード用のデバイスサポートの作り方を説明してきました。ここで紹介したデバイスサポートは、はじめにも書きましたようにほとんど秋山さんがお書きになったものです。飛山が変更したものは、当然のようにはじめは全く動きませんでした。これは、一つにはCプログラムが間違っていたこと、もう一つにはEPICSの流儀をまるでrebootされたかのように完璧に忘れていたからです。この際には秋山さんと山本さんに大変お世話になりました。これ以外にも、EPICSグループが作った各種のデバイスサポートがありますので、より難しいデバイスについてはそちらを参照するなり、コントロールグループの方に聞くなりして下さい。この文章が、皆様のお役に立てば幸いです。ご意見、ご感想等は、M.Tobiyamaまでお寄せ下さい。
Makoto Tobiyama
29/Oct/97

Return to FB Home Page...