CH552Tを用いたGPIBアダプタ


これまでに様々なGPIBアダプタを作ってきたが,低コストで楽にGPIBアダプタを作れないかと考えていたところ,WCH社のCH552Tを使うと新たなものが作れると気がついた. CH552TはUSBと直接接続でき,それ以外に14pinの入出力と1pinの入力を扱うことができる. GPIBは,8本のデータバスと,8本の制御線からなる. GPIBのコントローラーを作るには,12本の入出力,3本の出力,1本の入力が必要である. これをCH552Tで実現するためには,足りない1本の線を省略しなければならない. 省略できる線としては,三つの可能性が考えられる. ASCIIデータのみを扱うことにして8bit目のDIO8を省略するか, Interface ClearをしないことにしてIFCを省略するか, Remote Enableをoffにするのを諦めてRENをGNDに落とすかである. この中で最もデメリットが少ないのは,最後の案であると思われるので,それを採用して,GPIBのコントローラーを作ってみた.

CH552TにUSBケーブルのGNDと5Vと二本のdata線を接続して,GNDと5Vの間およびGNDと3.3Vの間に0.1uFのコンデンサを繋ぐと,USBで認識できるようになる. そして,省略するRENと入力のSRQ以外の14本の信号線に,14本の入出力ピンを割り当てる. SRQには入力ピンであるRSTピンを使うが,そのためにch55xtoolなどを使ってRST機能を無効にしなければならない. CH552TのパッケージはSSOP20であるが,DIPへの変換基板を使うと,24ピンのアンフェノール(セントロニクス)コネクタにギリギリ入るので,コネクタからUSBケーブルが出ているだけの,スッキリとした形にできる.

あとはプログラムであるが,Arduino UNOやnano用に書いたものをch55xduino用に書き換えた. SerialからUSBSerialになるので,ボーレートの設定は不要である. GPIBの信号線は,プルアップしてオープンドレインとすると良いが,これはpinModeをINPUT_PULLUPとすると実現できる. 他にもいくつかの変更を行ったが,折角なのでシリアルポールの機能も追加してみた. その機能はまだ動作確認をしていないので,うまく動くかは分からないが. そのプログラムが以下のようなものである.

// USB to GPIB converter using CH552T

#include <string.h>

#define DIO1 32  // CH552T  1, GPIB 1  : I/O data bit 1
#define DIO2 14  // CH552T  2, GPIB 2  : I/O data bit 2
#define DIO3 15  // CH552T  3, GPIB 3  : I/O data bit 3
#define DIO4 16  // CH552T  4, GPIB 4  : I/O data bit 4
#define EOI  17  // CH552T  5, GPIB 5  : End Or Identify
#define DAV  10  // CH552T  7, GPIB 6  : DAta Valid
#define NRFD 11  // CH552T  8, GPIB 7  : Not Ready For Data
#define NDAC 31  // CH552T  9, GPIB 8  : Not Data ACcepted
#define IFC  30  // CH552T 10, GPIB 9  : InterFace Clear
#define SRQ bRST // CH552T  6, GPIB 10 : Service ReQuest
#define ATN  33  // CH552T 11, GPIB 11 : ATteNtion
// shield GND    //USB shield, GPIB 12
#define DIO5 12  // CH552T 17, GPIB 13 : I/O data bit 5
#define DIO6 13  // CH552T 16, GPIB 14 : I/O data bit 6
#define DIO7 35  // CH552T 13, GPIB 15 : I/O data bit 7
#define DIO8 34  // CH552T 12, GPIB 16 : I/O data bit 8
#define REN   9  // CH552T 18, GPIB 17 : Remote ENable  9:non-existing pin
// GND           // CH552T 18, GPIB 18-23
// signal GND    // CH552T 18, GPIB 24
// CH552 Vcc(19)-0.1uF-GND(18)-0.1uF-V33(20)

//to use RST, please set EN_RST_RESET=0 using ch55xtool.
//ch55xtool -p -a Dis_RST_RESET --cfgs_options_force_action

#define GPIBTIMEOUT 200000 //200ms

__xdata char com[256] = "";
__xdata char delim[]="\r\n";
int p=0; // counter

char set_num(uint8_t ch,char num){ // INPUT_PULLUP, nagative logic
 if(num==0){digitalWrite(ch, HIGH); return digitalRead(ch);} //high and read
 else      {digitalWrite(ch, LOW); return 0;} //low
}
uint8_t readSRQ(){
  __data uint8_t bit = SRQ;
  __data uint8_t portBuf = CLOCK_CFG;
  if (portBuf & bit) return HIGH;
  else return LOW;
}

void gpibLineStatus(void){
  USBSerial_print("DAV=");    USBSerial_print(set_num(DAV,0));
  USBSerial_print(", NDAC="); USBSerial_print(set_num(NDAC,0));
  USBSerial_print(", NRFD="); USBSerial_print(set_num(NRFD,0));
  USBSerial_print(", IFC=");  USBSerial_print(set_num(IFC,0));
  USBSerial_print(", REN=");  USBSerial_print(set_num(REN,0));
  USBSerial_print(", SRQ=");  USBSerial_print(readSRQ());
  USBSerial_print(", ATN=");  USBSerial_print(set_num(ATN,0));
  USBSerial_print(", EOI=");  USBSerial_println(set_num(EOI,0));
  USBSerial_print(", DIO8-1=");
  USBSerial_print(set_num(DIO8,0)); USBSerial_print(set_num(DIO7,0));
  USBSerial_print(set_num(DIO6,0)); USBSerial_print(set_num(DIO5,0));
  USBSerial_print(set_num(DIO4,0)); USBSerial_print(set_num(DIO3,0));
  USBSerial_print(set_num(DIO2,0)); USBSerial_println(set_num(DIO1,0));
}
byte get_dio() { //negative logic
  byte x = 0;
  bitWrite(x, 0, !set_num(DIO1,0)); bitWrite(x, 1, !set_num(DIO2,0));
  bitWrite(x, 2, !set_num(DIO3,0)); bitWrite(x, 3, !set_num(DIO4,0));
  bitWrite(x, 4, !set_num(DIO5,0)); bitWrite(x, 5, !set_num(DIO6,0));
  bitWrite(x, 6, !set_num(DIO7,0)); bitWrite(x, 7, !set_num(DIO8,0));
  return x;
}
void set_dio(byte x) { //negative logic in set_num
  set_num(DIO1,bitRead(x,0)); set_num(DIO2,bitRead(x,1));
  set_num(DIO3,bitRead(x,2)); set_num(DIO4,bitRead(x,3));
  set_num(DIO5,bitRead(x,4)); set_num(DIO6,bitRead(x,5));
  set_num(DIO7,bitRead(x,6)); set_num(DIO8,bitRead(x,7));
}

void gpibAttention(){ // attention
  set_num(ATN,1); delayMicroseconds(20);
}
void gpibAttentionEnd(){ // end of attention
  set_num(ATN,0); delayMicroseconds(20);
}
boolean gpibWait(int ch, char hl){
  unsigned long count=GPIBTIMEOUT;
  while(hl == digitalRead(ch) && --count){delayMicroseconds(1);}
  if(count==0){USBSerial_println("timeout error");}
  return count==0;
}

boolean gpibWrite(byte data) {// true if error
  set_num(NDAC,0);
  if(gpibWait(NDAC,HIGH)){return true;} //until (LOW == NDAC)
  set_dio(data); // output data to DIO
  set_num(NRFD,0);
  if(gpibWait(NRFD,LOW)){return true;} //until (HIGH == NRFD)
  set_num(DAV,1); // validate data
  if(gpibWait(NDAC,LOW)){return true;} //until (HIGH == NDAC)
  set_num(DAV,0);
  set_dio(0);
  delayMicroseconds(30);
  return false;
}
boolean gpibRead(byte *data) {
  boolean ret;
  set_num(NRFD,0); // prepare to listen
  set_num(DAV,0);
  if(gpibWait(DAV,HIGH)){*data=0;return true;} //until (LOW == DAV)
  set_num(NRFD,1); // Ready for data
  *data = get_dio(); // read from DIO
  set_num(EOI,0);
  ret=(LOW == digitalRead(EOI)); // check EOI
  set_num(NDAC,0); // data accepted
  if(gpibWait(DAV,LOW)){*data=0;return true;} //until (HIGH == DAV)
  set_num(NDAC,1);
  return ret; // return true when EOI==LOW or error
}

void gpibTalk(byte addr, char *str) {
  set_num(EOI,0);
  gpibAttention();
  if(gpibWrite(0x5F)){gpibAttentionEnd();return;} // untalk
  if(gpibWrite(0x3F)){gpibAttentionEnd();return;} // unlisten
  if(gpibWrite(0x40)){gpibAttentionEnd();return;} // talker address
  if(gpibWrite(0x20+addr)){gpibAttentionEnd();return;} // listener address
  gpibAttentionEnd();
  // write string
  while (0 != *str) {
    if(0==*(str+1)){set_num(EOI,1);} // end of string
    if(gpibWrite(*(str++))){set_num(EOI,0);return;};
  }
  set_num(EOI,0);
  USBSerial_println("OK");
}
void gpibListen(byte addr, char *str) {
  *str=0;
  set_num(EOI,0);
  gpibAttention();
  if(gpibWrite(0x5F)){gpibAttentionEnd();return;} // untalk
  if(gpibWrite(0x3F)){gpibAttentionEnd();return;} // unlisten
  if(gpibWrite(0x40+addr)){gpibAttentionEnd();return;} // talker address
  if(gpibWrite(0x20)){gpibAttentionEnd();return;} // listener address
  set_num(NRFD,1); set_num(NDAC,1);
  delayMicroseconds(10);
  gpibAttentionEnd();
  // recieve data
  p = strlen(delim);
  do {
    if(gpibRead(str++)){*str=0;break;} //EOI
    *str=0;
  } while (strcmp(str-p, delim));
}
void gpibSerialPoll(byte addr, char *str) { //read one status byte
  *str=0;
  gpibAttention();
  if(gpibWrite(0x5F)){gpibAttentionEnd();return;} // untalk
  if(gpibWrite(0x3F)){gpibAttentionEnd();return;} // unlisten
  if(gpibWrite(0x18)){gpibAttentionEnd();return;} // Serial Poll Enable
  if(gpibWrite(0x40+addr)){gpibAttentionEnd();return;} // talker address
  set_num(NRFD,1); set_num(NDAC,1);
  delayMicroseconds(10);
  gpibAttentionEnd();
  gpibRead(str++); *str=0;
  gpibAttention();
  if(gpibWrite(0x19)){gpibAttentionEnd();return;} // Serial Poll Disable
  if(gpibWrite(0x5F)){gpibAttentionEnd();return;} // untalk
  gpibAttentionEnd();
}

void gpibIFC(void) {
  set_num(IFC,1); //set_num(REN,0);
  delayMicroseconds(128);
  set_num(IFC,0); USBSerial_println("IFC");
}
void gpibREN(char value) {
  set_num(REN,value); USBSerial_println("REN");
}
void gpibSRQ(void) {
  USBSerial_print("SRQ="); USBSerial_println(readSRQ());
}
void gpibCmd(char cmd, __code char *str){
  gpibAttention();
  if(gpibWrite(cmd)){gpibAttentionEnd();return;}
  gpibAttentionEnd();
  USBSerial_println(str);
}
void gpibCmdAdr(char addr,char cmd, __code char *str){
  gpibAttention();
  if(gpibWrite(0x3F)){gpibAttentionEnd();return;} // unlisten
  if(gpibWrite(0x20+addr)){gpibAttentionEnd();return;} // listener address
  if(gpibWrite(cmd)){gpibAttentionEnd();return;}
  gpibAttentionEnd();
  USBSerial_println(str);
}
void gpibDelim(char par){
       if(par==0){delim[0]=10,delim[1]=0;} // LF
  else if(par==1){delim[0]=13,delim[1]=0;} // CR
  else if(par==2){delim[0]=13,delim[1]=10;} // CR LF
  USBSerial_println("change delimiter");
}

void setup() {
//  USBSerial.begin(115200);
  // INPUT_PULLUP open-drain with pull-up
  pinMode(DIO1,INPUT_PULLUP); pinMode(DIO5,INPUT_PULLUP);
  pinMode(DIO2,INPUT_PULLUP); pinMode(DIO6,INPUT_PULLUP);
  pinMode(DIO3,INPUT_PULLUP); pinMode(DIO7,INPUT_PULLUP);
  pinMode(DIO4,INPUT_PULLUP); pinMode(DIO8,INPUT_PULLUP);
  pinMode(EOI, INPUT_PULLUP); pinMode(REN, INPUT_PULLUP);
  set_num(DIO1, 0); set_num(DIO5, 0);
  set_num(DIO2, 0); set_num(DIO6, 0);
  set_num(DIO3, 0); set_num(DIO7, 0);
  set_num(DIO4, 0); set_num(DIO8, 0);
  set_num(EOI,  0); set_num(REN,  0);
  pinMode(DAV, INPUT_PULLUP); pinMode(NRFD,INPUT_PULLUP);
  pinMode(NDAC,INPUT_PULLUP);
  set_num(DAV,  0);  set_num(NRFD, 1);
  set_num(NDAC, 1);
  //set_num(SRQ, 0); //RST
  pinMode(IFC, INPUT_PULLUP); pinMode(ATN, INPUT_PULLUP);
  set_num(IFC,  0); set_num(ATN,  0);
}

void loop() {
  if (USBSerial_available()==0){return;}
  if (0x0a != (com[p++] =USBSerial_read())) {return;}
  com[p]=0;
  if(com[0]<'a'){ // upper case
    com[3]=0; // three character commands
         if (0 == strcmp("IFC", com)){ gpibIFC();} // InterFace Clear
    else if (0 == strcmp("SRQ", com)){ gpibSRQ();} // Service ReQuest
    else if (0 == strcmp("REN", com)){ gpibREN(1);} // Remote ENable
    else if (0 == strcmp("RDA", com)){ gpibREN(0);} // Remote DisAble
    else if (0 == strcmp("LLO", com)){ gpibCmd(0x11,"LLO");} // Local Lock Out
    else if (0 == strcmp("DCL", com)){ gpibCmd(0x14,"DCL");} // Device CLear
    else if (0 == strcmp("LIN", com)){ gpibLineStatus();}
  }else{ // lower case
    p=(com[1]-'0')*10+com[2]-'0'; // two character address or parameter
    if(p<0 || p>31){USBSerial_println("address error");p=0;return;}
         if ('l' == com[0]){gpibCmdAdr(p,0x01,"GTL");} //l99 Go To Local
    else if ('c' == com[0]){gpibCmdAdr(p,0x04,"SDC");} //c99 Selected Device Clear
    else if ('t' == com[0]){gpibCmdAdr(p,0x08,"GET");} //t99 Group Executed Trigger
    else if ('d' == com[0]){gpibDelim(p);} //d00-02 delimiter
    else if ('w' == com[0]){gpibTalk(p, com+3);} //w99option
    else if ('r' == com[0]){gpibListen(p, com);USBSerial_print(com);} //r99
    else if ('s' == com[0]){gpibSerialPoll(p, com);USBSerial_print(com[0]);} //s99
  }
  p=0;
}

このプログラムをArduinoで書き込むと,RST機能が有効になるので,その後でRST機能を無効に設定すると良い. これをPCに接続すると,シリアルとして認識されるので,そこから適切なコマンドを送受信することによって,GPIBを使うことができる.Linuxの場合は,/dev/ttyACM0として認識されるが,そのままだとechoなどが有効になっているので,適切なモードに設定しなければならない. 最初はttyUSB0とは違ってモードの設定は不要だと思ってしていなかったら,不思議な挙動をして,悩んでしまった.

web上にはCH552を使ったGPIBアダプタの情報が見つからなかったが,この情報が同じようなことをする人の役に立つかも知れない. プログラムに関しては,私のプログラムは独自のプロトコルだが,他のプロトコル用に書き換えるのもそれほど難しくは無いだろう.

この自作のGPIBアダプタは,低コストで作りやすいという点で,これを越えるのは難しいかも知れない. しかし,いくつかの改良できる点は残されている. まず,専用の基板を設計して,配線のハンダ付けの数を減らすと,さらに作りやすくなるはずである. KiCADで,それっぽいものは作ってみたが,気に入らない点もあるので,もう少し改善できたら,基板を作ってみるかもしれない. アダプタの機能としては,binary転送に対応できていないという欠点もあるので,それもいずれは対応したい. 入出力ピンとしては,それに対応できるように接続しているので,後はソフトだけの問題なのだが,自分が必要としていないので,なかなかプログラムを書く気にならない. また,USBはシリアル通信として使っているが,USBをもっと自由度の高い使い方をすれば,本当のGPIBアダプタに近い動作を簡単にさせることできるように思う. しかし,それを実現するためには,USBのことをもっと勉強しないといけないだろう.