前の報告の時も、書きながら自分の物忘れのすごさにあきれていましたが、今回は全く感服するほどなにも覚えていませんでしたので、自分の備忘録代わりに簡単な事も含めて記録しておくことにします。
では、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ディレクトリで作ることにします。
VMEバスの詳細について興味がある方は、以下の本が参考になると思います。
- VMEバス・システム完全マスタ/岡村周善 インターフェース1987年2月号p192から
まだ、VMEバスが日の出の勢いだったころの記事。特に、VMEボードを作る人は必読と思う。
- 最適なVMEシステムの構築/徳田雅史他 インターフェース1993年4月号p83から
前のより、規格がより厳格になり、また新しくVXIバスも出てきたところです。ボードのデバッグなどに非常に役に立ちます。でもこれ以降、インターフェースでVMEバス特集はやっていないと思う。(PCIバス特集とかはありましたが)このことが、日本でのVMEバスのむごい動向を如実に表していると思う。これから産業用バスがどうなるかについての観測記事が最近のトランジスタ技術誌にのっていました。やはりそうか...
- Digital BUS HANDBOOK Joseph Di Giacomo (Editor), McGraw-Hill ISBN 0-07-016923-3
Stanford Bookstoreで$69.5で買ってきた本。VMEbus以外にIBM MCA、Multibus I+II、NuBus、DECバス、Futurebus、Fastbusの説明がある。はっきり言って、「わしらのわかいころには、いろんなばすがおってのう、木炭バスじゃろう、ボンネットバスじゃろう・・・」という趣の本です。これからの若い人には、やはりPCI(特にCompactPCI)とかUSBとかの方が、勉強しがいがあるでしょうなあ。
TD4-Vは標準特権データモードあるいは拡張特権データモードで動作しますが、ここでは標準特権モード用のデバイスサポートを作ります。TD4-Vの現在のコマンドは、以下の通りです。
アドレス | 入出力 | 機能
|
---|
0x*******0 | R/W | プリセット入力、設定値の表示
|
0x*******4 | R/W | Enable/Disableの設定、表示
|
0x*******8 | R | 最終設定値、現場指示値
|
いずれも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();
このデバイスサポートが許す、デバイスアクセス構造を定義します。例えば、
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_ASCENDING | 0x3f | 標準特権ブロック転送
|
VME_AM_STD_SUP_PGM | 0x3e | 標準特権プログラムアクセス
|
VME_AM_STD_SUP_DATA | 0x3d | 標準特権データ・アクセス
|
VME_AM_STD_USR_ASCENDING | 0x3b | 標準非特権ブロック転送
|
VME_AM_STD_USR_PGM | 0x3a | 標準非特権プログラム・アクセス
|
VME_AM_STD_USR_DATA | 0x39 | 標準非特権データ・アクセス
|
|
VME_AM_SUP_SHORT_IO | 0x2d | ショート特権アクセス
|
VME_AM_USR_SHORT_IO | 0x29 | ショート非特権アクセス
|
|
VME_AM_EXT_SUP_ASCENDING | 0x0f | 拡張特権ブロック転送
|
VME_AM_EXT_SUP_PGM | 0x0e | 拡張特権プログラム・アクセス
|
VME_AM_EXT_SUP_DATA | 0x0d | 拡張特権データ・アクセス
|
VME_AM_EXT_USR_ASCENDING | 0x0b | 拡張非特権ブロック転送
|
VME_AM_EXT_USR_PGM | 0x0a | 拡張非特権プログラム・アクセス
|
VME_AM_EXT_USR_DATA | 0x09 | 拡張非特権データ・アクセス
|
前から続く...
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アドレスとの対応が出来ました。
devLiTd4v構造体の中で、init_recordという部分がありましたが、この内容を記述します。今回はなにもしませんから、中身は空です。
static long init_record(plongin)
struct longinRecord *plongin;
{
return(0);
}
データベースで指定したカード番号の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レコードを通して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値を読みます。
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でロックを解除しているところです。
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をとれば良いのです。
デバイスサポートをコンパイルします。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ディレクトリの構造が正しく出来ているかチェックして下さい。なにが正しいか分からない人は、名人上手にお伺いをたてて下さい。
データベースを作ります。ディレクトリを~/epics.td4/dbにうつり、作業を始めます。今回はXDCTを使うことにします。(というか、取り敢えずこれしか覚えていないです。)
XDCT $DISPLAY &
とします。メニューでCreate a Process Databaseを選び、新しくデーベースを作ります。XDCTはカーソルで動き、終了はctrl-Eです。今回は、td4v01という名前のデータベースとします。このあと、process variableを作っていきます。今、2台のTD-4V(カード番号0と1)を使うとすると以下のようなデータベースを作ることになります。(この名前はとても良くない例です。本当は、コントロールグループがお決めになった正しい命名規則に従う必要があります。そうでないと、あちこちで名前がぶつかるという不幸な結果に見舞われるでしょう)
データタイプ | 名前 | Device Type | Card | Signal | HOPR | Zero Name | One Name
|
---|
bi | td4v01:td4status | TD-4V | 0 | | | Enable | Disable
|
bo | td4v01:td4enable | TD-4V | 0 | | | Enable | Disable
|
longin | td4v01:td4lastpreset | TD-4V | 0 | 1 | 65535 | |
|
longin | td4v01:td4preset | TD-4V | 0 | 0 | 65535 | |
|
longout | td4v01:td4out | TD-4V | 0 | | 65535 | |
|
|
bi | td4v02:td4status | TD-4V | 1 | | | Enable | Disable
|
bo | td4v02:td4enable | TD-4V | 1 | | | Enable | Disable
|
longin | td4v02:td4lastpreset | TD-4V | 1 | 1 | 65535 | |
|
longin | td4v02:td4preset | TD-4V | 1 | 0 | 65535 | |
|
longout | td4v02:td4out | TD-4V | 1 | | 65535 | |
|
inputのスキャンタイプは、例えば1秒とか10秒とかにします。Device typeのselectionでTD-4Vがでてこない場合は、makesdrがうまくいっていないので、あきらめてもう一度前からやり直すしかありません。初期化、link等を付けたいひとは、どうぞお付け下さい。難しいことをする場合は、capfastとか使った方がわかりやすいかもしれませんね。
データーベースが出来たら、これをコンバートします。
/cont/epics/local/etc/dct2db td4v01
とかすると、dbファイルができます。
スタートアップファイル(初期値はstartup.frc40)を編集して、データベースの読み込みと、デバイスサポートの登録、devTd4vの初期化等を行うように設定します。次のリンクを参考にして下さい。
スタートアップファイル(startup.vme)
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端末をつないで、設定をやり直す必要があります。これがまたコネクタが異常な格好をしているので、コントロールの山本さんに頼るしか助かる道はありません。なお、この状態から電気を入れておけば電池が復活するのかどうかについては、私は知りません。たとえ二次電池としても、放電させてしまった罪はきわめて重いと思われます。ごめんなさい許して下さい。
unixの上からcaget、caputコマンドを使い、動作を確認しましょう。例えば、プリセットの値を変えたいときは、
caput td4v01:td4out 5
とかすると、1番目のTD-4Vのプリセット値が変わるはずです。ここでaccess faultで落っこちたりすると、やはり何かが間違っています。値のモニターは
caput td4v01:td4preset.PROC 1
caget td4v01:td4preset
とすると、現在の値を読んできます。1行目は、データベースの更新を強制的にさせていることになります。ここまで出来てうまくいっているようでしたら、ご自分の好きな、medmなり、ぱいそんなり、sadでコントロールプログラムを作ればよいでしょう。例として、medmで作ったパネルを示します。
TD-4Vのデバイスサポートを例に、自分で作ったVMEボード用のデバイスサポートの作り方を説明してきました。ここで紹介したデバイスサポートは、はじめにも書きましたようにほとんど秋山さんがお書きになったものです。飛山が変更したものは、当然のようにはじめは全く動きませんでした。これは、一つにはCプログラムが間違っていたこと、もう一つにはEPICSの流儀をまるでrebootされたかのように完璧に忘れていたからです。この際には秋山さんと山本さんに大変お世話になりました。これ以外にも、EPICSグループが作った各種のデバイスサポートがありますので、より難しいデバイスについてはそちらを参照するなり、コントロールグループの方に聞くなりして下さい。この文章が、皆様のお役に立てば幸いです。ご意見、ご感想等は、M.Tobiyamaまでお寄せ下さい。
Makoto Tobiyama
29/Oct/97
Return to FB Home Page...