[KEKB Bunch Feedback Group]

Internix社製16チャンネル対応16ビットローコストA/D変換ボードPVME-331用EPICS Device Supportの製作(Japanese)


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

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


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

1.はじめに

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

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

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

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

2.VMEバスレジスタ

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

3.EPICS環境

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

4.割り込みのサポート

PVME-331は、インターバルタイマーを設定していない場合、トリガーがかかるまで待機をしており、トリガーにより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アドレスの割り当て構造体

アドレスマップにそって、DPvme331構造体を定義します。
struct DPvme331 {
  unsigned char   dummy0;
  unsigned char   scan_file0;
  unsigned char   dummy1;
  unsigned char   scan_file1;
  unsigned char   dummy2;
  unsigned char   scan_file2;
  unsigned char   dummy3;
  unsigned char   scan_file3;
  unsigned char   dummy4;
  unsigned char   scan_file4;
  unsigned char   dummy5;
  unsigned char   scan_file5;
  unsigned char   dummy6;
  unsigned char   scan_file6;
  unsigned char   dummy7;
  unsigned char   scan_file7;
  unsigned char   dummy8;
  unsigned char   scan_file8;
  unsigned char   dummy9;
  unsigned char   scan_file9;
  unsigned char   dummy10;
  unsigned char   scan_file10;
  unsigned char   dummy11;
  unsigned char   scan_file11;
  unsigned char   dummy12;
  unsigned char   scan_file12;
  unsigned char   dummy13;
  unsigned char   scan_file13;
  unsigned char   dummy14;
  unsigned char   scan_file14;
  unsigned char   dummy15;
  unsigned char   scan_file15;
  unsigned char   dummy16;
  unsigned char   trg_cntrl_reg;
  unsigned char   dummy17;
  unsigned char   trg_strt_reg;
  unsigned char   dummy18;
  unsigned char   sts_reg;
  unsigned char   dummy19;
  unsigned char   scn_cnt_reg;
  unsigned char   dummy20;
  unsigned char   mode_reg;
  unsigned char   dummy21;
  unsigned char   rst_reg;
  unsigned char   dummy22;
  unsigned char   int_cntrl_reg;
  unsigned char   dummy23;
  unsigned char   int_vct_reg;
  unsigned char   dummy24;
  unsigned char   int_sts_reg;
  unsigned short  dummy25;
  unsigned short  intvl_tm_reg;
  unsigned short  data_reg;
  unsigned short  dummys[100];
};    
カード毎の占有アドレススペースは0xffとります。

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

このデバイスサポートで使用する関数のプロトタイプ宣言をしておきます。
int devPvme331Config();

static long init_all();

static long init_wf_record();
static long init_bi_record();
static long init_bo_record();

static long read_wf_record();
static long get_wf_int_info();
static long read_bi_record();
static long write_bo_record();
static void pvme331_isr();

static int  checkLink();

devPvme331Configはスタートアップルーチンで呼びますので、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;
}devWfPvme331 ={
  6,
  NULL,
  NULL,
  init_wf_record,
  get_wf_int_info,
  read_wf_record,
  NULL
};

です。

5.5 カード構造体の宣言

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

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

int devPvme331Config(ncards,a24base,intvecbase,Nchannel)
int ncards;
long a24base;
int intvecbase;
int Nchannel;
{
  pvme331_num_links = ncards;
  Base_IO = a24base;
  INT_VEC_BASE = intvecbase;
  pvme_adc = Nchannel;
  logMsg("Pvme331 NumLink= %d BaseIO= %x INTVECBASE= %x ADC= %d Ch\n",pvme331_num_links,Base_IO,INT_VEC_BASE,pvme_adc);
  init_all(0);
}

何枚PVME-331を使うか、Base_IOアドレス、interruptベクター、スキャンチャンネルを設定するファイルです。スタートアップファイルの中で、
devPvme331Config(1,0x800000,0xe1,16)
iocInit
の様に、iocInitの前に呼びます。

5.7 初期化ファイル

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

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

  init_flag = 1;

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

  for (cardNum=0; cardNum< pvme331_num_links; cardNum++)
   {
     if (vxMemProbe((char*) &(p->rst_reg), READ, 1, &probeVal)!=OK)
       {
	 if (debug_flag >0 ) 
         logMsg("No PVME331 with cardNum= %d\n probe= %x\n",cardNum,p);
	 cards[cardNum].card = NULL;
       }
     else
       {
	 probeVal=0x0f;
	 if ((vxMemProbe((char*) &(p->rst_reg), WRITE,1,&probeVal)==OK)&&(debug_flag >5)) 
	   logMsg("System Reset for card %d\n",cardNum);
	 nanosleep(4000000);
	 probeVal=BIP10 | 0x00;
	 if ((vxMemProbe((char*) &(p->scan_file0), WRITE,1,&probeVal)==OK)&&(debug_flag >5)) 
	   logMsg("Scan file 0 set  %d\n",cardNum);
	 probeVal=BIP10 | 0x01;
	 if ((vxMemProbe((char*) &(p->scan_file1), WRITE,1,&probeVal)==OK)&&(debug_flag >5)) 
	   logMsg("Scan file 1 set  %d\n",cardNum);
	 probeVal=BIP10 | 0x02;

・・・中略・・・
	 cards[cardNum].card = p;  /* Remember address of the board */

	 scanIoInit(&cards[cardNum].ioscanpvt);
	 logMsg("pvme331 ioInit. 0x%X\n",cards[cardNum].ioscanpvt);

	 FASTLOCKINIT(&(cards[cardNum].lock));
	 FASTUNLOCK(&(cards[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)pvme331_isr, 
		 (int)pwf) != OK)
     logMsg("devPVME331: Interrupt connect failed for card %d\n",pwf->inp.value.vmeio.card);
  logMsg("intConnect 0x%X\n",pwf);
	 
  sysIntEnable(int_level);
  logMsg("pvme331 int set for int 0x%x\n",int_level);
  /*  cards[cardNum].card->trg_cntrl_reg = 0x81;*/
  tg_mode = 0x80;
  tg_enable = 0x01;
  return(0);

}
intConnectで割り込みベクターと割り込みハンドラの関連づけを行います。その後sysIntEnableで割り込みを許可します。

5.9 割り込みハンドラ

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

  cardN = pwf->inp.value.vmeio.card;
  
  /*  logMsg("pvme331_isr int called \n");*/
  mode = cards[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;

  cardN = pwf->inp.value.vmeio.card;
  *ppvt = cards[cardN].ioscanpvt;
  logMsg("pvme331 intInfo. 0x%X\n",*ppvt);
  cards[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の場合、設定により相手は2の補数ですから、読みとった値をそのまま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[cardN].card->trg_cntrl_reg = 0x80;

  switch(pwf->ftvl)
    {
    case DBF_USHORT:
      /*	unsigned short* us_thing = (unsigned short*)pwf->bptr; */
	for (i=0; i<pwf->nelm;i++)
	  {
	    samples=cards[cardN].card->data_reg;
	    us_thing[i]=(unsigned short)(samples);
	  }
	pwf->nord=i;
	cards[cardN].card->trg_cntrl_reg = (tg_mode | tg_enable);
	break;
    case DBF_SHORT:
      /*	short* s_thing = (short*)pwf->bptr; */
	for (i=0; i<pwf->nelm;i++)
	  {
	    samples=cards[cardN].card->data_reg;
	    s_thing[i]=(short)(samples);
	  }
	pwf->nord=i;
	cards[cardN].card->trg_cntrl_reg = (tg_mode|tg_enable);
	break;
    default:
      logMsg("devPVME331: Invalid data type\n");
    }
  cards[cardN].card->rst_reg=0x04;
  cards[cardN].card->trg_cntrl_reg = (tg_mode|tg_enable);
  return(0);
}

6.EPICSデータベース

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

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

Makefile.Vxを変更し、それぞれ
SRCS.C += ../devPVME331.c
と
LIBOBJS += devPVME331.o
を付け加え、gmakeでコンパイルします。capfastのschファイルもsch2dbでdbファイルにコンバートします。また、新しく加わったデータベースを"なんとかinclude.dbd"の定義に加えておきます。
device(waveform,VME_IO,devWfPvme331,"PVME331")
device(bo,VME_IO,devBoPvme331,"PVME331")
device(bi,VME_IO,devbiPvme331,"PVME331")
スタートアップファイルは、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_adc.db","user=tobiyama")
#dbLoadRecords("feedbackApp/Db/dbExample.db","user=tobiyama")
devPvme332Config(1,0x800000,0xe1,16)
iocInit
#seq &snctest

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

8.MEDMによる制御

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

9.おわりに

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

Return to FB Home Page...