[KEKB Bunch Feedback Group]

Internix社製64チャンネル対応16ビット高精度A/D変換ボードPVME-332用EPICS Device Supportの製作(Japanese)


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

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


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

1.はじめに

PVME-332は、インターニックス株式会社殿が開発したVMEバス対応のシングルエンド入力時64チャンネル/差動入力時32チャンネルのアナログ入力、スループットレート15マイクロ秒/チャンネルの16ビット分解能A/D変換ボードです。

A/D変換器自体はボード上に1つしか載っていませんので、多チャンネルの読み込みはシーケンシャルオートスキャニングによって実現しています(読みとりチャンネル数は指定します)。A/D変換のトリガーは、外部入力(TTL立ち下がりエッジ)、ソフトウエア設定トリガー及びそれらの後のインターバルタイマーを使った自動トリガーが可能です。

これから紹介するデバイスサポートは、このボードを「とりあえず」EPICSで使うためにtobiyamaが書いたものです。既に多くの偉大な先達がお書きになったデバイスサポートを参考にしています。本ボードは色々な機能を持っていますが、以下のデバイスサポートは全てのモード変更には対応していません。

上記と違うモードで使用するためには、デバイスサポートを変更する必要があります。多分簡単です。

2.VMEバスレジスタ

PVME-332を操作する上で必要な各レジスタについて紹介します。詳しくはPVME-332のユーザーズ・マニュアルをご覧下さい。ベースアドレス(BASE_IO)は省略しますが、あるアドレス(例えば0x01)が書いてある場合、BASE_IO+0x01だと思って見て下さい。特に断らない限り、unsigned charアクセスをしていると思って下さい。

3.EPICS環境

本デバイスサポートは、EPICS R313用です(R312で動かない理由はないと思いますが)。EPICSそのものに対する説明、入門出家入道については専門家に帰依するなり、コントロールグループのページをご参照されるなり、各自ご努力ください。R312とR313はディレクトリ構造、ライブラリ構造とも大きく変わっていますのでご注意下さい。また、IOCは68k40及びPPC750で動作確認を行っています。
せっかちな人のために、以下にこれから紹介するデバイスサポートのsource fileを示します。

4.割り込みのサポート

PVME-332は、インターバルタイマーを設定していない場合、トリガーがかかるまで待機をしており、トリガーにより1チャンネルずつA/D変換、FIFOへのストアーを行います。全てのスキャンが終了した時点でデータを読み込むためには、sts_regを常に監視していてスキャン終了フラグが立つのを見るか、スキャン終了時に割り込みをかけ、割り込みサービスとしてデータ読み込みを行うか、ということになります。言うまでもなくマルチジョブ環境でスキャン終了フラグをモニターするのは論外ですから、割り込みをつかうことになります。
EPICS/VxWorksで割り込みを使う場合、以下のルーチンを用意する必要があります。 このデバイスサポートでは、データをwaveformレコードにいれますので、waveformの定義にこの割り込み部分を付け加える必要があります。

5.コードの説明

基本的なデバイスサポートの構造については、TD-4Vのデバイスサポートの項目で十分に?説明してありますので、ここでは目新しい物のみを説明します。

5.1 インクルードファイル

インクルードファイルでは、割り込み処理に関連して以下のheaderが新たに必要になります。(実はいらないかもしれない)
#include        <iv.h>
#include        <dbScan.h>

5.2 VMEアドレスの割り当て構造体

アドレスマップにそって、DPvme332構造体を定義します。
struct DPvme332 {
  unsigned char dummy0;
  unsigned char trg_cntrl_reg;
  unsigned char dummy1;
  unsigned char trg_strt_reg;
  unsigned char dummy2;
  unsigned char sts_reg;
  unsigned char dummy3;
  unsigned char scn_cnt_reg;
  unsigned char dummy4;
  unsigned char mode_reg;
  unsigned char dummy5;
  unsigned char ana_con_reg;
  unsigned char dummy6;
  unsigned char rst_reg;
  unsigned char dummy7;
  unsigned char int_cntrl_reg;
  unsigned char dummy8;
  unsigned char int_vct_reg;
  unsigned char dummy9;
  unsigned char int_sts_reg;
  unsigned short intvl_tm_reg;
  unsigned short data_reg;
  unsigned short dummys[116];
};
カード毎の占有アドレススペースは0xffとります。

5.3 関数プロトタイプの宣言

このデバイスサポートで使用する関数のプロトタイプ宣言をしておきます。あまりお行儀の良くない書き方で恐縮ですが、

int devPvme332Config();
static long init_all();
static long init_wf_record();
static long init_mi_record();
static long init_bo_record();
static long read_wf_record();
static long get_wf_int_info();
static long read_mbbiDirect();
static long write_bo_record();
static void pvme332_isr();

static int  checkLink();

を宣言しています。devPvme332Configはスタートアップルーチンで呼びますので、staticにしてはいけません。

5.4 データベース構造体の宣言

このデバイスサポートでは、waveform、bo、mbbiDirectのデータベースを使います。割り込みに関連しているのはwaveform recordです。

waveformレコードの定義は次の通りです。

struct {
    long        number;
    DEVSUPFUN   report;
    DEVSUPFUN   init;
    DEVSUPFUN   init_record;
    DEVSUPFUN   get_ioint_info;
    DEVSUPFUN   read_write;
    DEVSUPFUN   conv;
}devWfPvme332 ={
  6,
  NULL,
  NULL,
  init_wf_record,
  get_wf_int_info,
  read_wf_record,
  NULL
};

です。

5.5 カード構造体の宣言

struct ioCard {
  volatile struct DPvme332  *card;    /* address of this card */
  FAST_LOCK                 lock;     /* semaphore */
  IOSCANPVT                 ioscanpvt;/* list or records processed upon interrupt */
      };
と、通常の構造体のメンバーにIOSCANPVTを付け加える必要があります。また、これを使ったカード用変数は
static struct ioCard cards_332[CONST_NUM_LINKS];
です。

5.6 スタートアップで呼ぶ設定読み込みファイル

int devPvme332Config(ncards,a24base,irq,intvecbase,Nchannel)
int ncards;
long a24base;
int irq;
int intvecbase;
int Nchannel;
{
  pvme332_num_links = ncards;
  Base_IO = a24base;
  int_level = irq;
  INT_VEC_BASE = intvecbase;
  pvme_adc = Nchannel;
  logMsg("Pvme332 NumLink= %d BaseIO= %x INTVECBASE= %x ADC= %d Ch\n",pvme332_num_links,Base_IO,INT_VEC_BASE,pvme_adc);
  init_all(0);
}
何枚PVME-332を使うか、Base_IOアドレス、interruptレベル、interruptベクター、スキャンチャンネルを設定するファイルです。スタートアップファイルの中で、
devPvme332Config(1,0xaf6000,0x05,0xef,64)
iocInit
の様に、iocInitの前に呼びます。

5.7 初期化ファイル

カード存在の確認、各レジスタの初期化を行います。
static long init_all(after)
int after;
{
  short                        cardNum, chanNum;
  int                          i,j;
  unsigned char                probeVal;
  unsigned short               p1;
  volatile struct DPvme332     *p;
  char temp;

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

  init_flag = 1;

  if (sysBusToLocalAdrs(VME_AM_STD_SUP_DATA,(char *)Base_IO,(char **)&p) == ERROR)
	{
		logMsg("VmeP332: cannot find A24 address space\n");
		return(ERROR);
	}

  for (cardNum=0; cardNum< pvme332_num_links; cardNum++)
   {
     tg_mode = 0x81; /*external trigger enable  afterwards*/
     probeVal = TG_INT_NO; /* INT trigger -- trigger disable */
     if (vxMemProbe((char*) &(p->trg_cntrl_reg), WRITE, 1, &probeVal)!=OK)
       {
	 if (debug_flag >0 ) 
         logMsg("No PVME332 with cardNum= %d\n probe= %x\n",cardNum,p);
	 cards_332[cardNum].card = NULL;
       }
     else
       {
	 probeVal=0x06; /* adc calib */
	 if ((vxMemProbe((char*) &(p->rst_reg), WRITE,1,&probeVal)==OK)&&(debug_flag >5)) 
	   logMsg("ADC calibration for card %d\n",cardNum);
	 nanosleep(4000000);

......中略......

	 probeVal = INT_VEC_BASE+cardNum;
	  if ((vxMemProbe((char*) &(p->int_vct_reg), WRITE,1,&probeVal)==OK)&&(debug_flag >5)) 
	   logMsg("Int vector %x = %x\n",cardNum,probeVal);
	 	 if (debug_flag >0)
	   logMsg("Found PVME332 with cardNum= %d address= %x\n",cardNum,p);
	 cards_332[cardNum].card = p;  /* Remember address of the board */
	 scanIoInit(&(cards_332[cardNum].ioscanpvt));
	 cards_332[cardNum].card->int_cntrl_reg = (0x10) | (unsigned char)(int_level);

	 FASTLOCKINIT(&(cards_332[cardNum].lock));
	 FASTUNLOCK(&(cards_332[cardNum].lock)); /* Init the board lock */
       }
     p++;
   }
  return(OK);
}

以下の設定をします。 その後、fastlock等の初期化をします。

5.8 waveform recordの初期化

iocInitの前に割り込みがかかってしまうとosが死んでしまいますので、iocinitのあと、各データベースを初期化しているところでOSの割り込み関係の設定をします。
static long init_wf_record(pwf)
struct waveformRecord	*pwf;
{
  short cardNum;
  
  cardNum = pwf->inp.value.vmeio.card;

  if (intConnect(INUM_TO_IVEC(INT_VEC_BASE + cardNum),
		 (VOIDFUNCPTR)pvme332_isr, 
		 (int)pwf) != OK)
    logMsg("devPVME332: Interrupt connect failed for card %d\n",pwf->inp.value.vmeio.card);
  logMsg("intConnect 0x%X\n",pwf);
	 
  sysIntEnable(int_level);
  logMsg("pvme332 int set for int 0x%x\n",int_level);
  cards_332[cardNum].card->trg_cntrl_reg = 0x81;
  return(0);
}

intConnectで割り込みベクターと割り込みハンドラの関連づけを行います。その後sysIntEnableで割り込みを許可し、カードに対してはトリガーを外部トリガー、トリガー許可のモード(0x81)とします。

5.9 割り込みハンドラ

割り込みがかかったとき、割り込みの処理をするルーチンを記述します。このカードは割り込み処理がROAKなので、まずint_sts_regを読むことで割り込みを解除します。その後、scanIoRequestで実際の読みとり処理を行います。
static void pvme332_isr(pwf)
struct waveformRecord	*pwf;
{
  unsigned char mode;
  short cardN;

  cardN = pwf->inp.value.vmeio.card;
  
  /*  logMsg("pvme332_isr int called \n");*/
  mode = cards_332[cardN].card->int_sts_reg; 
  
  scanIoRequest(cards_332[cardN].ioscanpvt);
  /*  logMsg("pvme332 int called \n");*/
  return;
}
なお、この割り込みハンドラ中にprintfを書くとうまく動かないようです。メッセージが必要な場合はlogMsgを使います。但し、これはbufferedですので、例えばここでこけてもそのメッセージが表示されるかは不明です。

5.10 waveform record割り込み用初期化ファイル

scanIoRequestが動いたときに、waveformを読みに来る様に、ioscanpvtを設定します。
static long get_wf_int_info(cmd,pwf,ppvt)
int                    cmd;
struct waveformRecord  *pwf;
IOSCANPVT              *ppvt;
{
  short                cardN;
  unsigned char        stat; 

  cardN = pwf->inp.value.vmeio.card;
  *ppvt = cards_332[cardN].ioscanpvt;
  /* logMsg("pvme332 intInfo. 0x%X\n",*ppvt); */ 
  cards_332[cardN].card->trg_cntrl_reg = (unsigned char)((tg_mode)|(0x01));
  if (debug_flag >5) logMsg("wf_INT CALLED\n");
  return(0);
}

5.11 waveform record読みとりファイル

waveformにFIFOバッファーの内容を読みとっていきます。まず、トリガーを禁止し、ftvlのタイプを確認します。DBF_USHORTの場合、読みとるべき相手はストレートバイナリですから、そのままの値を読みとっていきます。DBF_SHORTの場合、相手はオフセットバイナリですから、読みとった値-32767をwaveformに入れていきます。いずれもnelmまで入れます。その後、FIFOバッファをクリアし、トリガーを許可します。
static long read_wf_record(pwf)
struct waveformRecord	*pwf;
{
  short cardN;
  int i;
  unsigned short samples;

  unsigned short* us_thing = (unsigned short*)pwf->bptr;
  short* s_thing = (short*)pwf->bptr;
	
  if (debug_flag >5)
    logMsg("read_wf_record called...\n");

  cardN = pwf->inp.value.vmeio.card;
  if (debug_flag >5)
    logMsg("read_wf_record called with card number of %d\n",cardN);

  if (checkLink(cardN) == ERROR)
    return(ERROR);
  cards_332[cardN].card->trg_cntrl_reg = 0x80 ; /* disable trigger */

  switch(pwf->ftvl)
    {
    case DBF_USHORT:
      /*	unsigned short* us_thing = (unsigned short*)pwf->bptr; */
	for (i=0; i<pwf->nelm;i++)
	  {
	    samples=cards_332[cardN].card->data_reg;
	    us_thing[i]=(unsigned short)(samples);
	  }
	pwf->nord=i;
	cards_332[cardN].card->trg_cntrl_reg = tg_mode | 0x01;
	break;
    case DBF_SHORT:
      /*	short* s_thing = (short*)pwf->bptr; */
      /* logMsg("short called \n"); */
	for (i=0; i<pwf->nelm;i++)
	  {
	    samples=cards_332[cardN].card->data_reg;
	    s_thing[i]=(short)(samples-32767);
	    /*  logMsg("FIFO status = %x\n",cards_332[cardN].card->sts_reg);*/
	  }
	pwf->nord=i;
	cards_332[cardN].card->trg_cntrl_reg = tg_mode |0x01;
	break;
    default:
      logMsg("devPVME332: Invalid data type\n");
    }
  cards_332[cardN].card->rst_reg = 0x04;
  cards_332[cardN].card->trg_cntrl_reg = (unsigned char)((tg_mode)|( 0x01));
  return(0);
}

6.EPICSデータベース

このデバイスサポートで使うデータベースをまとめると、以下の様になります。
DatabaseNameCSfunctionSCANDTYPその他
mbbi directstatus00status read10 secondPVME332
botrigger00triggerPassivePVME332
botg_inout01tg_inoutPassivePVME332ZNAM="EXT",ONAM="INT"
boadc_calib02A/D calibPassivePVME332
waveformadc_in00adc inI/O IntrPVME332FTVL="SHORT",NELM=64
subarraya00〜a6300adcPassiveSoft channelMALM=64,NELM=1,INDXを設定
なお、subarrayを使うためには、トリガー分配のためfanoutもたくさん必要になります。medmでデータを読んだりしない限り、subarrayはいらないかも知れません。(しかしデータベースで一般的に使うためには、どこかでwaveformをばらさなければならないので、結局は同じことになると思われる)。capfastで書いたデータベースを下に示します。

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

Makefile.Vxを変更し、それぞれ
SRCS.C += ../devPVME332.c
と
LIBOBJS += devPVME332.o
を付け加え、gmakeでコンパイルします。capfastのschファイルもsch2dbでdbファイルにコンバートします。また、新しく加わったデータベースを"なんとかinclude.dbd"の定義に加えておきます。
device(waveform,VME_IO,devWfPvme332,"PVME332")
device(bo,VME_IO,devBoPvme332,"PVME332")
device(mbbiDirect,VME_IO,devMbbiPvme332,"PVME332")
スタートアップファイルは、iocBootの下のiocなんとかの所に作ります。
# Example vxWorks startup file
#Following must be added for many board support packages
cd "/users/tobiyama/epics_r313/iocBoot/iocfeedback"
#ld < bin/enableA24_frc64.o
ld < bin/kekRouteSet_frc40.o
#ld < bin/kekRouteSet_frc64.o
ld < bin/iocCore
ld < bin/seq
ld < bin/feedbackLib

dbLoadDatabase("dbd/feedbackApp.dbd")
dbLoadDatabase("feedbackApp/Db/fb_ar_blm.db","user=tobiyama")
#dbLoadRecords("feedbackApp/Db/dbExample.db","user=tobiyama")
devPvme332Config(1,0xaf6000,0x05,0xef,64)
iocInit
#seq &snctest

iocInitの前にdevPvme332Configでパラメータを渡すのを忘れないようにして下さい。

8.MEDMによる制御

テストのためmedmで制御をしてみました。
medm image
入力はch0にのみfunction generatorからの0.1Hzの三角波を入れ、他のチャンネルはオープンになっています(こういう使い方はとてもよくない)。triggerはexternal triggerで50Hzでかけています。表示はch0とch1を見ていますが(赤がch0、青がch1)、ch1はオープンのため、ch0に引きずられてノイズが変動しています。

9.おわりに

汎用64チャンネル16ビットADC-PVME332のデバイスサポート及びEPICSデータベース等の説明を行いました。このデバイスサポートの開発においては、コントロールグループの皆様に相談に乗っていただき、色々なご協力を頂きました。感謝いたします。また、製造元のインターニックス殿及び販売代理店のロジックハウス殿にも色々ご協力を賜りました。感謝いたします。
Makoto Tobiyama
5/Sep/98

Return to FB Home Page...