最も安価なUSB-GPIBアダプタ


CH32V003にはUSBを扱う機能が無い. しかし,rv003usbを使うとlow speedのUSBを使うことができる. 20pinのICでは,電源とグランドとUSBの二本を除くと,16ピン残る. これは,GPIBに必要なピンの数と一致する. CH32V003は非常に安価なので,これを使ってUSB-GPIBアダプタを作れば,最も安くUSB-GPIBアダプタが作れるのでは無いかと思ったので,作ってみた.

rv003usbのインストールはdownloadしてunzipすればよいが,ch32funと組み合わせて使うので,近くのフォルダにすると便利である. プログラムは,通常はWCH-LinkEを用いて書き込みをするが,USBからプログラムを書くことができるbootloaderも作られており,最初以外はWCH-LinkEが不要になるので便利である.

bootloaderを入れるには,rv003usbのbootloaderフォルダに行って,usb_config.hでUSBに使うピンを指定する. 私の場合は以下のようにした.

#define USB_PORT A
#define USB_PIN_DP 2
#define USB_PIN_DM 1
//#define USB_PIN_DPU 5

そして,Makefileの中のCH32FUNに,../../ch32fun-master/ch32funなどとch32funのソースファイルの場所を指定する. すると,make cleanと初期化して,make buildでコンパイルできる. WCH-LinkEに接続してmakeとすると,minichlinkで書き込みが行われる.

USBケーブルとの接続は以下のようにする. USBの5Vから3.3Vを作って,それをCH32V003の電源に繋ぎ,USBのdata線は33ohmの抵抗を介してケーブルに接続する. Low speedであることを指定するために,USBDMは1.5kohmの抵抗を介して3.3Vに接続する. この状態でUSBにさすと,bootloaderが認識され,五秒後にユーザーコードが実行される. ch32funの書き込みツールであるminichlinkでプログラムを書き込むことができるので,この五秒間に書き込みを開始する.

rv003usbを使うのは今回は初めてだったので,使い方を調べながらGPIB用のプログラムを書いたが,予想していたよりも単純だった. まず,control transferとendpoint in/outを扱うために,usb_config.hで以下のように設定する.

#define RV003USB_HANDLE_IN_REQUEST 1
#define RV003USB_OTHER_CONTROL	   1
#define RV003USB_HANDLE_USER_DATA  1

usb_config.hには,descriptorの情報も記述する.

control transferのrequestはusb_handle_other_control_messageに何をするかを記述する. s->wRequestTypeLSBRequestMSBの下位バイトがRequestTypeで上位バイトがRequestなので,それらの値で分岐する. その際,s->lValueLSBIndexMSBの下位ワードがValueで上位ワードがIndexであるので,これらの値を必要に応じて使う. e->opaqueにはバッファ,e->max_lenにバイト数を指定すると,データを返すことができる. このバッファを示す変数に,関数の内部で定義した変数を使っていたら,関数が終わったときに開放されるのでうまく行かなかった. static volatileにしておく必要があるだろう.

endpointのINとOUTはそれぞれusb_handle_user_in_requestとusb_handle_user_dataに何をするかを書く. INの処理では,usb_send_data(buf,len,0,tok)でデータを送ることができる. ここで,bufはデータのあるバッファで,lenはそのバイト数である. 空のデータを返すときには,usb_send_empty(tok)を使う. NAKを返す場合にusb_send_data(0,0,2,0x5a)を使えば良いことが分かるまでには少し苦労した.

多少の試行錯誤の後に,なんとかvendor specificなUSB-GPIBアダプタが出来た. コネクタとケースを除いて価格を見積もってみると,基板が50円ぐらいで,CH32V003が50円ぐらい,三端子レギュレータとチップ抵抗とコンデンサで30円ぐらいだろうか. 以前作ったやつで使ったch552tが70円ぐらいで,チップコンデンサが5円ぐらいなので,そっちの方が僅かに安いかも知れないが,これはRENが制御できないという欠点がある. RENも制御できるものとしては,最安と言っても良いのでは無いだろうか. しかし,Low Speedなので,endpointのサイズが8byteしか無いという欠点もある. これらの欠点を解消した安いアダプターを作るには,USB機能のあるpin数の多い安いICを使う必要があるが,以前使ったCH32V203K8T6がその第一候補だろう. 価格は120円ほどなので,50円ほど高くなってしまうが,今度作ってみようと思う.

最後に少し長くなるが,ファイルの内容を書いておこうと思う. まず,Makefileでは,rv003usbを組み込むこと以外は,特記すべき点は無いが,以下の内容である.

all : flash

TARGET:=usb003gpib
CH32FUN:=../ch32fun-master/ch32fun
TARGET_MCU:=CH32V003

ADDITIONAL_C_FILES+=../rv003usb-master/rv003usb/rv003usb.S ../rv003usb-master/rv003usb/rv003usb.c
EXTRA_CFLAGS:=-I../rv003usb-master/lib -I../rv003usb-master/rv003usb

include $(CH32FUN)/ch32fun.mk

flash : cv_flash
clean : cv_clean

メインのプログラムであるusb003gpib.cの内容をこんな感じ.

#include "ch32fun.h"
#include "rv003usb.h"
#include <string.h>
#include <stdbool.h>

// CH32V003F4P6
#define GPIB_DIO1 PD0 //  8 GPIB 1  : I/O data bit 1
#define GPIB_DIO2 PD1 // 18 GPIB 2  : I/O data bit 2
#define GPIB_DIO3 PD2 // 19 GPIB 3  : I/O data bit 3
#define GPIB_DIO4 PD3 // 20 GPIB 4  : I/O data bit 4
#define GPIB_DIO5 PD4 //  1 GPIB 13 : I/O data bit 5
#define GPIB_DIO6 PD5 //  2 GPIB 14 : I/O data bit 6
#define GPIB_DIO7 PD6 //  3 GPIB 15 : I/O data bit 7
#define GPIB_DIO8 PD7 //  4 GPIB 16 : I/O data bit 8
#define GPIB_REN  PC0 // 10 GPIB 17 : Remote ENable
#define GPIB_EOI  PC7 // 17 GPIB 5  : End Or Identify
#define GPIB_DAV  PC6 // 16 GPIB 6  : DAta Valid
#define GPIB_NRFD PC5 // 15 GPIB 7  : Not Ready For Data
#define GPIB_NDAC PC4 // 14 GPIB 8  : Not Data ACcepted
#define GPIB_IFC  PC3 // 13 GPIB 9  : InterFace Clear
#define GPIB_SRQ  PC2 // 12 GPIB 10 : Service ReQuest
#define GPIB_ATN  PC1 // 11 GPIB 11 : ATteNtion

// control request commands
#define GPIB_DIO_LINE  0x40+0
#define GPIB_EOI_LINE  0x40+5
#define GPIB_DAV_LINE  0x40+6
#define GPIB_NRFD_LINE 0x40+7
#define GPIB_NDAC_LINE 0x40+8
#define GPIB_IFC_LINE  0x40+9
#define GPIB_SRQ_LINE  0x40+10
#define GPIB_ATN_LINE  0x40+11
#define GPIB_REN_LINE  0x40+17
#define GPIB_READ    0x80
#define GPIB_WRITING 0x81
#define GPIB_READING 0x82
#define GPIB_READY   0x83
#define GPIB_LEN     0x84
#define GPIB_EOS     0x85
#define GPIB_REOS    0x86
#define GPIB_TIMEOUT 0x87
#define GPIB_TTLSZ   0x88

#define BUFFER_SIZE 8

static uint8_t gpib_buf[BUFFER_SIZE];
static uint8_t data[2];
static volatile int send_index = 0;

static volatile uint8_t _writing = false;
static volatile uint8_t _reading = false;
static volatile uint8_t _ready = false;
static volatile uint8_t _len = 0;
static volatile uint8_t _eos = 0x0a; // end of string
static volatile uint8_t _reos = false; // read end of string
static volatile uint16_t _timeout = 1000; // ms
static volatile uint16_t _ttlsz = 0; // total size to read

#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) ((bitvalue) ? bitSet(value, bit) : bitClear(value, bit))

uint8_t set_num(uint32_t ch, uint8_t num){ // nagative logic
 if(num==0){funPinMode(ch, GPIO_CFGLR_IN_FLOAT); return funDigitalRead(ch);} //high and read
 else      {funDigitalWrite(ch, FUN_LOW); funPinMode(ch, GPIO_CFGLR_OUT_50Mhz_PP); return 0;} //low
}

static void gpib_setup(void)
{ // GPIO_CFGLR_IN_FLOAT, GPIO_CFGLR_OUT_50Mhz_PP
  funGpioInitC();
  funGpioInitD();
// disable NRST to use PD7, by ./minichlink -D
  Delay_Ms(1000); // 1s
// disable SWIO to use PD1
  AFIO->PCFR1&=~AFIO_PCFR1_SWCFG;
  AFIO->PCFR1|=AFIO_PCFR1_SWCFG_2;
// init
  set_num(GPIB_DIO1, 0); set_num(GPIB_DIO5, 0);
  set_num(GPIB_DIO2, 0); set_num(GPIB_DIO6, 0);
  set_num(GPIB_DIO3, 0); set_num(GPIB_DIO7, 0);
  set_num(GPIB_DIO4, 0); set_num(GPIB_DIO8, 0);
  set_num(GPIB_EOI,  0); set_num(GPIB_REN,  0);
  set_num(GPIB_DAV,  0); set_num(GPIB_NRFD, 1);
  set_num(GPIB_NDAC, 1); set_num(GPIB_SRQ,  0);
  set_num(GPIB_IFC,  0); set_num(GPIB_ATN,  0);
}

uint8_t gpib_write_dio(uint8_t x) { //negative logic in set_num
  set_num(GPIB_DIO1,bitRead(x,0)); set_num(GPIB_DIO2,bitRead(x,1));
  set_num(GPIB_DIO3,bitRead(x,2)); set_num(GPIB_DIO4,bitRead(x,3));
  set_num(GPIB_DIO5,bitRead(x,4)); set_num(GPIB_DIO6,bitRead(x,5));
  set_num(GPIB_DIO7,bitRead(x,6)); set_num(GPIB_DIO8,bitRead(x,7));
  return x;
}
uint8_t gpib_read_dio() { //negative logic
  uint8_t x = 0;
  bitWrite(x,0,!set_num(GPIB_DIO1,0)); bitWrite(x,1,!set_num(GPIB_DIO2,0));
  bitWrite(x,2,!set_num(GPIB_DIO3,0)); bitWrite(x,3,!set_num(GPIB_DIO4,0));
  bitWrite(x,4,!set_num(GPIB_DIO5,0)); bitWrite(x,5,!set_num(GPIB_DIO6,0));
  bitWrite(x,6,!set_num(GPIB_DIO7,0)); bitWrite(x,7,!set_num(GPIB_DIO8,0));
  return x;
}
bool gpib_wait(uint16_t ch, uint8_t hl){
  unsigned long count=_timeout;
  count*=1000; // ms to us
  while(hl == funDigitalRead(ch) && --count){Delay_Us(1);}
  return count==0; // true if timeout
}

bool gpib_write_byte(uint8_t data) {// return true if error
  set_num(GPIB_NDAC,0);
  if(gpib_wait(GPIB_NDAC,FUN_HIGH)){return true;} //until (FUN_LOW == NDAC)
  gpib_write_dio(data); // output data to DIO
  set_num(GPIB_NRFD,0);
  if(gpib_wait(GPIB_NRFD,FUN_LOW)){return true;} //until (FUN_HIGH == NRFD)
  set_num(GPIB_DAV,1); // validate data
  if(gpib_wait(GPIB_NDAC,FUN_LOW)){return true;} //until (FUN_HIGH == NDAC)
  set_num(GPIB_DAV,0);
  gpib_write_dio(0);
  //  delayMicroseconds(30);
  return false;
}
bool gpib_read_byte(uint8_t *data) {// return true when end or error
  bool ret;
  set_num(GPIB_NRFD,0); // prepare to listen
  set_num(GPIB_DAV,0);
  set_num(GPIB_EOI,0);
  if(gpib_wait(GPIB_DAV,FUN_HIGH)){return true;} //until (FUN_LOW == DAV)
  set_num(GPIB_NRFD,1); // Ready for data
  *data = gpib_read_dio(); // input data from DIO
  ret=(FUN_LOW == set_num(GPIB_EOI,0)); // check EOI
  set_num(GPIB_NDAC,0); // data accepted
  if(gpib_wait(GPIB_DAV,FUN_LOW)){return true;} //until (FUN_HIGH == DAV)
  set_num(GPIB_NDAC,1);
  ret |= _reos && ( *data == _eos); // end of string
  return ret;
}

uint16_t gpib_write(uint8_t* buffer, uint16_t len){
  uint16_t pos=0;
  _writing=true;
  for(pos=0;pos<len && !gpib_write_byte(buffer[pos]);pos++);
  _writing=false;
  return _len=pos;
}
int16_t gpib_read(uint8_t* buffer, uint16_t len){
  int16_t pos=0;
  for(pos=0;pos<len;pos++){
    if(pos==_ttlsz){ _reading=false; break; }
    if( gpib_read_byte(buffer+pos) ){ //end
      _reading=false;
      ++pos;
      break;
    }
  }
  _ttlsz-=pos;
  return pos;
}


// called when USB endpoint receives OUT data from host
void usb_handle_user_data(struct usb_endpoint *e, int ep, uint8_t *data, int len, struct rv003usb_internal *ist)
{
  if (ep == 1 && len > 0) gpib_write(data,len);
}

// called when host requests IN data on USB endpoint
void usb_handle_user_in_request(struct usb_endpoint *e, uint8_t *scratchpad, int ep, uint32_t tok, struct rv003usb_internal *ist)
{
  if (ep != 1) {
    usb_send_empty(tok);  // wrong EP
  } else if(_ready) { // data ready
    usb_send_data((uint8_t*)(gpib_buf), send_index, 0, tok);
    _ready=false;
  } else if(_reading) { // not data ready but still reading
    usb_send_data( 0, 0, 2, 0x5a ); // Send NAK
  } else {
    usb_send_empty(tok); // no more data
  }
}

// called on any other USB control message
void usb_handle_other_control_message(struct usb_endpoint *e, struct usb_urb *s, struct rv003usb_internal *ist)
{
  uint16_t value;
  uint8_t len=1;
  if( (s->wRequestTypeLSBRequestMSB & 0xe0) ==0xc0){ //RequestType
    value=(s->lValueLSBIndexMSB) & 0xffff;
    switch( (s->wRequestTypeLSBRequestMSB) >>8){ //Request
    case GPIB_DIO_LINE  : data[0]=(value)? gpib_write_dio(value) : gpib_read_dio(); break;
    case GPIB_EOI_LINE  : data[0]=set_num(GPIB_EOI,value); break;
    case GPIB_DAV_LINE  : data[0]=set_num(GPIB_DAV,value); break;
    case GPIB_NRFD_LINE : data[0]=set_num(GPIB_NRFD,value); break;
    case GPIB_NDAC_LINE : data[0]=set_num(GPIB_NDAC,value); break;
    case GPIB_IFC_LINE  : data[0]=set_num(GPIB_IFC,value); break;
    case GPIB_SRQ_LINE  : data[0]=set_num(GPIB_SRQ,value); break;
    case GPIB_ATN_LINE  : data[0]=set_num(GPIB_ATN,value); break;
    case GPIB_REN_LINE  : data[0]=set_num(GPIB_REN,value); break;
    case GPIB_READ    : gpib_read_byte(data); break;
    case GPIB_WRITING : if(value)_writing=value & 0xff; data[0]=_writing; break;
    case GPIB_READING : if(value)_reading=value & 0xff; data[0]=_reading; break;
    case GPIB_READY   : if(value)_ready=value & 0xff; data[0]=_ready; break;
    case GPIB_LEN     : if(value)_len=value & 0xff; data[0]=_len; break;
    case GPIB_EOS     : if(value)_eos=value & 0xff; data[0]=_eos; break;
    case GPIB_REOS    : if(value)_reos=value & 0xff; data[0]=_reos; break;
    case GPIB_TIMEOUT : if(value)_timeout=value; data[0]=_timeout&0xff; data[1]=_timeout>>8;len=2; break;
    case GPIB_TTLSZ   : if(value)_ttlsz=value; data[0]=_ttlsz&0xff; data[1]=_ttlsz>>8;len=2; break;
    default: len=0;
    }
  } else {
    len=0;
  }
  e->opaque =  data;
  e->max_len = len;
}

int main(void)
{
  SystemInit();
  usb_setup();
  gpib_setup();
  while (1) {
    if(_reading && !_ready){
      send_index=gpib_read(gpib_buf,BUFFER_SIZE);
      _ready=true; // data ready for IN
    }
  }
}

ch32funの設定ファイルfunconfig.hは,大した内容では無いが,内容は以下の通り.

#ifndef _FUNCONFIG_H
#define _FUNCONFIG_H

#define FUNCONF_USE_DEBUGPRINTF 0
#define CH32V003                1
#define FUNCONF_SYSTICK_USE_HCLK 1

#endif

rv003usbの設定ファイルusb_config.hには,descriptorの情報が含まれているので,長くなってしまっている.

#ifndef _USB_CONFIG_H
#define _USB_CONFIG_H

//Defines the number of endpoints for this device. (Always add one for EP0). For two EPs, this should be 3.
#define ENDPOINTS 3

//#define SOP8
#define QFN20

#ifdef SOP8
#define USB_PIN_DP 2
#define USB_PIN_DM 1
//#define USB_PIN_DPU 4 
#define USB_PORT A
#else
#ifdef QFN20
#define USB_PIN_DP 2
#define USB_PIN_DM 1
//#define USB_PIN_DPU 5
#define USB_PORT A
#else
#error "NO FORMAT DEFINED, please specify one in usb_config.h"
#endif
#endif

#define RV003USB_OPTIMIZE_FLASH    0
#define RV003USB_HANDLE_IN_REQUEST 1
#define RV003USB_OTHER_CONTROL	   1
#define RV003USB_HANDLE_USER_DATA  1
#define RV003USB_HID_FEATURES	   0
#define RV003USB_EVENT_DEBUGGING   0

#ifndef __ASSEMBLER__

#include <tusb_types.h>
//#include <cdc.h>
//#define INSTANCE_DESCRIPTORS
#ifdef INSTANCE_DESCRIPTORS

static const uint8_t device_descriptor[] = {
	18, //Length
	TUSB_DESC_DEVICE,  //Type (Device)
	0x10, 0x01, //Spec
	TUSB_CLASS_VENDOR_SPECIFIC, //Device Class
	MISC_SUBCLASS_COMMON, //Subclass
	0x00, //Device Protocol
	0x08, //Max packet size for EP0 (This has to be 8 because of the USB Low-Speed Standard)
	0x48, 0x43, //ID Vendor
	0x37, 0x55, //ID Product
	0x10, 0x01, //ID Rev
	1, //Manufacturer string
	2, //Product string
	3, //Serial string
	1, //Max number of configurations
};

static const uint8_t config_descriptor[] = {
	// configuration descriptor, USB spec 9.6.3, page 264-266, Table 9-10

	9,					  // bLength;
	TUSB_DESC_CONFIGURATION,  // bDescriptorType;
	32, 0x00,				  // wTotalLength
	0x01,					  // bNumInterfaces (Normally 1)
	0x01,					  // bConfigurationValue
	0x00,					  // iConfiguration
	0x80,					  // bmAttributes (was 0xa0)
	0x64,					  // bMaxPower (200mA)

	9,						  // bLength
	TUSB_DESC_INTERFACE,	  // bDescriptorType
	0,						  // bInterfaceNumber
	0,						  // bAlternateSetting
	2,						  // bNumEndpoints
	TUSB_CLASS_VENDOR_SPECIFIC, 	// bInterfaceClass
	0,  // bInterfaceSubClass
	0,    		   // bInterfaceProtocol
	0x00,		  // iInterface (For getting the other descriptor)

	7,		  // endpoint descriptor (For endpoint 1)
	TUSB_DESC_ENDPOINT,   // Endpoint Descriptor
	USB_DIR_IN +1,	      // Endpoint Address
	TUSB_XFER_BULK,		  // Attributes
	0x08,	0x00,		  // Size
	0,			  // Interval

	7,			  // endpoint descriptor (For endpoint 1)
	TUSB_DESC_ENDPOINT,   // Endpoint Descriptor
	USB_DIR_OUT +1,		  // Endpoint Address
	TUSB_XFER_BULK,		  // Attributes
	0x08,	0x00, // Size
	0,			  // Interval

};

#define STR_MANUFACTURER u"ch32"
#define STR_PRODUCT 	 u"usb gpio"
#define STR_SERIAL	 u"0314"

struct usb_string_descriptor_struct {
	uint8_t bLength;
	uint8_t bDescriptorType;
	uint16_t wString[];
};
const static struct usb_string_descriptor_struct string0 __attribute__((section(".rodata"))) = {
	4,
	TUSB_DESC_STRING,
	{0x0409}
};
const static struct usb_string_descriptor_struct string1 __attribute__((section(".rodata")))  = {
	sizeof(STR_MANUFACTURER),
	TUSB_DESC_STRING,
	STR_MANUFACTURER
};
const static struct usb_string_descriptor_struct string2 __attribute__((section(".rodata")))  = {
	sizeof(STR_PRODUCT),
	TUSB_DESC_STRING,
	STR_PRODUCT
};
const static struct usb_string_descriptor_struct string3 __attribute__((section(".rodata")))  = {
	sizeof(STR_SERIAL),
	TUSB_DESC_STRING,
	STR_SERIAL
};



// This table defines which descriptor data is sent for each specific
// request from the host (in wValue and wIndex).
const static struct descriptor_list_struct {
	uint32_t	lIndexValue;
	const uint8_t	*addr;
	uint8_t		length;
} descriptor_list[] = {
	{0x00000100, device_descriptor, sizeof(device_descriptor)},
	{0x00000200, config_descriptor, sizeof(config_descriptor)},

	{0x00000300, (const uint8_t *)&string0, 4},
	{0x04090301, (const uint8_t *)&string1, sizeof(STR_MANUFACTURER)},
	{0x04090302, (const uint8_t *)&string2, sizeof(STR_PRODUCT)},	
	{0x04090303, (const uint8_t *)&string3, sizeof(STR_SERIAL)}
};
#define DESCRIPTOR_LIST_ENTRIES ((sizeof(descriptor_list))/(sizeof(struct descriptor_list_struct)) )

#endif // INSTANCE_DESCRIPTORS

#endif

#endif