最も安価な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