AR大電流実験は1996年12月2日をもって、大成功裏(???)に赫々たる大成果をあげ(???)終了しました。 この第3期には、多くのフィードバック用機器をリングに設置しましたが、未だ経験のない、かなり危ないデバイスを大バンチ電流、大平均電流環境下に無慈悲にさらすため、機器の、主にパワーの出入りに関する箇所(フィードスルー等)の温度をモニターする こととしました。 ローカルな温度モニターシステム自身は、測温抵抗体をハイブリッドレコーダーで 計るだけのもので、配線設置がとても面倒くさいことを除けば本質的な大きな問題はありません。 今回は欲を出して、ハイブリッドレコーダーをGP-IBから読んでやり、ネットワークでモニターできるEPICS温度モニターシステムを作ることとしました。 こうしておけば、コントロール室や居室など、ネットワークが届くところならどこでも温度監視が出来るだろうという目論見でした。
このメモで述べる作業は1996年10月ころに(既に期間もあやしい)行いました。 すぐにせめてメモでも残しておけば良かったのですが、なにかと取り紛れているうちに(というより、未だかつてなく殺人的に忙しかった)、月日は無情にもあれよあれよと 過ぎてゆき、記憶ももはや極めて怪しいこととなってしまいました。 しかしながらこのまま放っておくと遠からず完全に忘れてしまうのは明らかでありますので、せめてここで覚えていることだけでも(主に自分のために)メモに残すこととしました。
なお、私が作業した時点と今では状況はきっと変わっているに違いなく、記憶も極めて曖昧でありますから、このメモを信用するような向こう見ずな大それたことをしてはいけません。 必す専門の先生方に確認の上、必要な準備あるいは作業をしてください。 また、EPICSの基礎とか、準備とか、入門とか、出家入道とかについては、特に必要なもの以外は一切ふれません。 この部分について、知識あるいは助けが必要な方は、コントロールグループの方々、各グループリンクパーソン殿あるいは経験者にお聞きください。
GP-IB機器をEPICSで使おうという大それたことをする場合、このデバイスサポートは各自が用意しなければなりません(実は他のcamacやvmeではどうなのかは知らずにこんなことを書いている)。 このデバイスサポートは、昔は呪われたもろCで書いていたらしく不評だったそうですが、私はGP-IB device Decsription Language(GDL)で作ることになりました。 このGDLの仕様については関東情報サービスの吉田奨氏のレポートを参照してください。 確かに楽にはなってはいますが、やはりかなりのCの覚悟及び技術が必要です。
データ収集の要求コマンドは幾種かありますが、ここではgroup番号で指定された10チャンネル分のデータを応答させるコマンドを使います。
外部→MH2000
| 11 | , | チャンネルグループ番号 | , | デリミタ |
|---|
ここでチャンネルグループ番号は
01: CH 01から10まで 02: CH 11から20まで 03: CH 21から30まで実はチャンネルは60までありますが、31から60は演算結果を入れるためのもので、今回は使用しません。MH2000の応答は、エラーがないときは
MH2000→外部
| 11 | , | チャンネルグループ番号 | , | データ1 | , | データ2 | , | ... | , | データ10 | , | デリミタ |
|---|
となります。データは1チャンネルあたり、下記の12バイトで構成されています。
A1 A2 A3 A4 S D1 D2 D3 D4 D5 D6 D7 A1〜A4は警報発生情報をレベル毎(4レベル)示すもので、 0 : 未発生 1 : 上限警報 2 : 下限警報 3 : 変化率上限警報 4 : 変化率下限警報 5 : 差上限警報 6 : 差下限警報 Sはデータの状態を示し、 0 : 正常 1 : 断線(burn out) 2 : 入力オーバーフロー 3 : 入力アンダーフロー 4 : データーオーバーフロー 5 : データーアンダーフロー 9 : 無効データ D1〜D7で、データを7桁で右詰に示します。データが上の様な、尋常でない形をしているので、読み込んだデーターから温度部分を取り出す必要があります。
STATIC float get_onetemp(char wa1[], int *s)
{
#define E_TEMP -1000.0
*s = (wa1[4]-'0');
if (*s != 0) return E_TEMP;
else return atof(wa1+5);
}
STATIC int rd_temp(char wav[], float temp[], int s[])
{
int i;
char wa1[255];
#define NAK 0x15
if (wav[0]!=NAK)
{
strtok(wav,",");
strtok(NULL,",");
for (i=0; i<10; i++)
{
strcpy(wa1,strtok(NULL,","));
temp[i] = get_onetemp(wa1,&s[i]);
}
return 0;
}
else
{
s[0] = atoi(wav+1);
return 1;
}
}
function rd_tempで読み込んだデータ(全部で10ポイントのデータを含んでいる)を、カンマ毎に分け、get_onetempでヘッダをチェックし、正しいデータなら温度部分を取り出します。何らかのエラーがあったら、-1000度に設定します。
パラメータテーブルには、GP-IBコマンドを記述します。
ParamTable{
read_temp1 {rec=wf, command="11,01,\r\n",
leng=255, conv=rd_wf_temp}
read_temp2 {rec=wf, command="11,02,\r\n",
leng=255, conv=rd_wf_temp}
read_temp3 {rec=wf, command="11,03,\r\n",
leng=255, conv=rd_wf_temp}
}
各チャンネルグループ毎にコマンドを書きます(デリミタも)。
EPICSレコードタイプのうち、使えるのはwaveformとなりますが、この場合は必ずCファンクションで変換規則ファンクションを記述する必要があります。
変換は
STATIC int rd_wf_temp(struct gpibDpvt *pdpvt, int p1, int p2, char **p3)
{
struct waveformRecord * pwf = (struct waveformRecord *) (pdpvt->precord);
char *craw;
float *temparray;
float temp[255],*ptemp;
int s[255],ok_flag;
unsigned long numElem;
temparray = (float *) pwf->bptr;
craw = pdpvt->msg;
craw = strtok(craw,"\n\r");
/* printf("input data: %s\n",craw); */
if (craw == NULL) /* fairly unlikely ... */
{
devGpibLib_setPvSevr(pwf, READ_ALARM, INVALID_ALARM);
return(ERROR);
}
ok_flag = rd_temp(craw,temp,s);
if (ok_flag != 0)
{
devGpibLib_setPvSevr(pwf,READ_ALARM, INVALID_ALARM);
return(ERROR);
}
numElem = 10;
if (numElem > pwf->nelm) numElem = pwf->nelm;
pwf->nord = numElem;
ptemp=temp;
while (numElem--)
{
/* printf( "\n%d-th data: %f",numElem, *ptemp); */
*temparray++ = (float) *ptemp++;
}
/* printf( "\nread_wf end normally\n"); */
return(OK);
}
のようなコードになります。なにを隠そう、実はとびやまもこの詳細を完全に理解している訳ではなく、サンプルファイルをもとに作っただけです。
機器から送り返されたデータは、msgフィールドに入っているので、それをcrawに入れ、処理をします。結果はbptrに入れて返します。型はfloat型になっていますが、これはデータベース側と一致させる必要があります。
一応エラー処理をしているように見えますが、エラーが起きたことがないので、これでいいのか分かりません。全体はファイル名
という名前でセーブしておきます。なお、このソースが気に入らない方は、こっそりとびやままでお知らせください。
# Can link multiple objects: #test: test1.o test2.o # $(LOAD) -o test test1.o test2.o devMH2000Gpib.o:$(SRC)/devMH2000Gpib.gt $(MAKE_GPIB)$(MAKE_GPIB)のまえの空白は、タブでないといけないらしい。
/*** COMMAND LIST ***/ No. 0 WF read_temp1 No. 1 WF read_temp2 No. 2 WF read_temp3となる。みんなWFなので単純明快ですね(おおうそ)。
"waveform" ET488_IO "devWfMH2000Gpib" "MH2000"
GP-IBデバイスサポートが使うレコードはwaveform型だけだが、これを1点ずつ表示するためには、ai型が必要です。 このwaveformからai型への変換は次に述べるシーケンサーを使って行いますが、シーケンサーを起動するタイミングを作るため、calcも使います。
作成したGP-IBデバイスサポートは、温度出力を、結局float型の配列にしてwaveformに入れて返すことになっています。各waveformには10点分の温度が入っています。 計、60点分必要なので、6個作ります。(temp01からtemp06まで)
propertyは次のようにしました(変更箇所のみ)。
DTYP : MH2000 FTVL : FLOAT HOPTR : 100 LOPTR : 20 SCAN : 10 secondまた、dev(INP)にはGP-IBのアドレス、パラメータ番号を #L0 A3 P0 の様に指定します。 ここでL?はGP-IBコントローラーのリンク番号、A?はそのデバイスのGP-IBアドレス、P?はGP-IBデバイスサポートで登録したパラメータテーブルのパラメータ番号でになります。temp01からtemp06までは次のようになります。(1台目のMH2000のGP-IBアドレスは3、2台目のMH2000は4にしました)
temp01 : #L0 A3 P0 temp02 : #L0 A3 P1 temp03 : #L0 A3 P2 temp04 : #L0 A4 P0 temp05 : #L0 A4 P1 temp05 : #L0 A4 P2
シーケンサーがwaveformの値変化をモニターできるか分からなかった(多分だめ)ので、calcを使い、waveformに値が入り次第変化する部分を作り、シーケンサーのトリガーにします。 具体的には、waveformのFLNKとcalcのSLNKを線でつなぎ、calcのVALとINPAも線でつなぎます。 calcフィールドは{A>10}?0:A+1とかして、waveformに値が入り次第、Aを増やし、10を越えたら0にするようにします。これをDCTで記述することは可能ではありますが、 一見してわかりにくく、ミスが怖いとのことでした。なお、式の記述方法は、DCTとcapfastでやや異なります。名前は、tempm01からtempm06までとしました。
各温度ポイントに対応して、t00からt59まで60個用意します。フィールドの値は
DTYP : Soft Channel LOPR : 20 HOPR : 100 SCAN : Passiveとしました。
完成したら、fb_temp_arという名前でsaveし、convertでデータベースに変換します。 私が作業したときは、capfastもconvertもsad2の上でしか動きませんでした。今はどうなっているか知りません。
はじめに、使う変数を宣言します。
int i1; float TEMP_01[10]; float CTEMP_01[10]; int CMON_01; int CMONP_01 = 0; ..まだまだつづく...次に、変数をデータベースのレコードにassignします。
assign TEMP_01 to "fb_temp_ar:temp01";
assign CTEMP_01 to {"fb_temp_ar:t00","fb_temp_ar:t01",
"fb_temp_ar:t02","fb_temp_ar:t03",
"fb_temp_ar:t04","fb_temp_ar:t05",
"fb_temp_ar:t06","fb_temp_ar:t07",
"fb_temp_ar:t08","fb_temp_ar:t09"};
assign CMON_01 to "fb_temp_ar:tempm01";
..まだまだつづく...
次に、モニター(変化を監視する)を宣言します。
monitor CMON_01; monitor CMON_02; monitor CMON_03; monitor CMON_04; monitor CMON_05; monitor CMON_06;この後、シーケンサー動作を記述します。なお、ssは並列で動作しますが、ss内のstateは上から順に動作していくので(シーケンサーだから)、上が動かないから動かないという間抜けなことにならないよう注意します。今回は、すべて並列動作ということになります(だからシーケンサーではない)。
ss state_main1
{
state sta1
{
when(CMON_01!=CMONP_01)
{
%{
printf("state 1 on\n");
}%
pvGet(TEMP_01);
for (i1=0; i1<10; i1++)
{
CTEMP_01[i1] = TEMP_01[i1];
pvPut(CTEMP_01[i1]);
}
CMONP_01 = CMON_01;
}state sta1
}
}
..まだまだつづく..
CMON_01の値が変わったら(=waveformに新しい値が入った)、変換を行うプログラムになっています。
データのinportおよびexportにpvGetとpvPutを使っていることに注意してください。全体のソースを見たいひとはこちらをバシバシたたいて下さい。
完成したシーケンサーをコンパイルするため、Makefileを変更し、makeします。
#cd to application directory #cd "myappl" cd "/users/tobiyama/epics_appl" pwd
# load any application objects here # or code for subroutine records here #ld < ./objfrc40/testsub.o ld < ./objfrc40/devMH2000Gpib.o
# load sequences here #ld < ./objfrc40/testseq.o ld < ./objfrc40/temp_ar.o
#dbLoadRecords "db/testdb.db" dbLoadRecords "gdct/fb_temp_ar.db"
#start up ET-488
init_ET488("192.153.111.45","1024",0)
#now execute sequences #seq &testseq seq &seq_ar_temp
いまとなっては、それほど「うなる」作業であったかは、もはや辛い記憶も定かではないのでなんともいえませんが(大体、地獄の大電流実験の苦しみさえも忘れかけている)、特に手間がかかった部分を述べると、
作業にあたりましては、多くの方の多大なご支援をいただきました。デバイスサポートの変換プログラムに関しては、RFの赤坂展昌さんに相談にのっていただきました。 作業の最初にコントロールグループの駒田一孝さんに相談にのっていただきました。 コントロールグループの山本昇さんにははじめから最後までいろいろ教えていただき、またお手数をおかけしました。関東情報サービスの吉田奨さんにはつきっきりでいろいろ教えていただきました。これらの方々の暖かいご支援がなければ、決して実用的には動かなかったと思われます。深く感謝いたします。
Makoto Tobiyama