Arduinoを使ってUSB-HUB経由でUSBシリアル通信を行う方法です。私がいつも使わせてもらっているArduinoは中国製クローンで、USB-UART変換チップには本家Arduinoと異なりCH340というICが使われています(本家はFTDI製IC)。しかしUSB Host Library2.0にはCH340用ドライバがありません。そこでCH340のLinuxドライバをポーティングしてみました。これでUSB-HUBにArduinoクローンを複数ぶら下げるよう構成が実現出来るはずです。
環境
ハードウェア
- Arduino uno R3互換品(Atmega328)
- Arduino USB Host Shield互換品
- Arduino nano互換品
- USB2.0 HUB
開発環境
- PlatformIO
IDE:2.0.0-beta.2
Core:3.4.0a8
atmelavr:1.4.5
toolchain-atmelavr:1.40902.0
framework-arduinoavr:1.10617.4
tool-avrdude:1.60300.0 - USB Host Library2.0
CH340のディスクリプタテーブルを確認する
冒頭でも述べましたが、今回対象としているUSBシリアル変換ICはCH340です。安価なArduinoクローンなどのマイコンボードでよく見かけるチップで、Windows7で使う場合でも別途ドライバをインストールする必要がありました。これをArduino + USB Host Shieldと通信させたいわけです。
CH340と通信するのに、まずディスクリプタテーブルの情報を見てみることにしました。テーブルにはプロトコルなどの情報が載っているので参考になるはずです。
USB Host Library2.0のサンプルスケッチを見てみると接続したUSBデバイスのディスクリプタテーブルを表示してくれるものがありました(以下のものです)。これを書き込んでからCH340が載っているArduino nanoを接続してみました。
- USB_Host_Shield_2.0\examples\hub_demo
出力されたログを見てみると"Intf. Class:FF"となっていて、通信プロトコルはベンダーオリジナルのようです。このあたりはFTDI製でも同じようですが、そうなると何処かでCH340のドライバーソースコードを探してくる必要がありそうです。
--- String Descriptors: Product: USB2.0-Serial Device descriptor: Descriptor Length: 12 Descriptor type: 01 USB version: 0110 Device class: FF Device Subclass: 00 Device Protocol: 00 Max.packet size: 08 Vendor ID: 1A86 Product ID: 7523 Revision ID: 0254 Mfg.string index: 00 Prod.string index: 02 Serial number index: 00 Number of conf.: 01 Configuration descriptor: Total length: 0027 Num.intf: 01 Conf.value: 01 Conf.string: 00 Attr.: 80 Max.pwr: 30 Interface descriptor: Intf.number: 00 Alt.: 00 Endpoints: 03 Intf. Class: FF Intf. Subclass: 01 Intf. Protocol: 02 Intf.string: 00 Endpoint descriptor: Endpoint address: 82 Attr.: 02 Max.pkt size: 0020 Polling interval: 00 Endpoint descriptor: Endpoint address: 02 Attr.: 02 Max.pkt size: 0020 Polling interval: 00 Endpoint descriptor: Endpoint address: 81 Attr.: 03 Max.pkt size: 0008 Polling interval: 01 ---
Linux版ドライバをポーティングする
探してみるとメーカーからCH340/CH341のLinux版ソースコードが公開されていました。この処理をArduinoのUSB Host Library2.0に移植してやれば動きそうです。
- WCH - CH341 LINUX SOURCE CODE
http://www.wch.cn/download/CH341SER_LINUX_ZIP.html - USB Host Library 2.0
https://github.com/felis/USB_Host_Shield_2.0
ドライバは既存のFTDIドライバをひな型として使わせてもらい、そこに上記ソースコードから処理を持ってくることにしました。処理内容としては送受信部分はFTDIと共通(エンドポイントに対していBulk転送するだけ)で、CH340固有処理としては初期化とボーレートやらパリティビット等の設定部分だけのようです。
今回作成したのは以下のファイルです。使用する際はUSB Host Library2.0と同じフォルダに配置してください。
- cdc_ch34x.cpp
- cdc_ch34x.h
cdc_ch34x.cpp
#include "cdc_ch34x.h" const uint8_t CH34X::epDataInIndex = 1; const uint8_t CH34X::epDataOutIndex = 2; const uint8_t CH34X::epInterruptInIndex = 3; CH34X::CH34X(USB *p, CDCAsyncOper *pasync) : pAsync(pasync), pUsb(p), bAddress(0), bNumEP(1), ready(false){ for(uint8_t i = 0; i < CH34X_MAX_ENDPOINTS; i++) { epInfo[i].epAddr = 0; epInfo[i].maxPktSize = (i) ? 0 : 8; epInfo[i].bmSndToggle = 0; epInfo[i].bmRcvToggle = 0; // epInfo[i].bmNakPower = (i==epDataInIndex) ? USB_NAK_NOWAIT: USB_NAK_MAX_POWER; epInfo[i].bmNakPower = (i==epDataInIndex) ? USB_NAK_DEFAULT : USB_NAK_MAX_POWER; } if(pUsb) pUsb->RegisterDeviceClass(this); } uint8_t CH34X::Init(uint8_t parent, uint8_t port, bool lowspeed) { const uint8_t constBufSize = sizeof (USB_DEVICE_DESCRIPTOR); uint8_t buf[constBufSize]; USB_DEVICE_DESCRIPTOR * udd = reinterpret_cast<USB_DEVICE_DESCRIPTOR*>(buf); uint8_t rcode; UsbDevice *p = NULL; EpInfo *oldep_ptr = NULL; uint8_t num_of_conf; // number of configurations AddressPool &addrPool = pUsb->GetAddressPool(); USBTRACE("CH34X Init\r\n"); if(bAddress) return USB_ERROR_CLASS_INSTANCE_ALREADY_IN_USE; // Get pointer to pseudo device with address 0 assigned p = addrPool.GetUsbDevicePtr(0); if(!p) return USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL; if(!p->epinfo) { USBTRACE("epinfo\r\n"); return USB_ERROR_EPINFO_IS_NULL; } // Save old pointer to EP_RECORD of address 0 oldep_ptr = p->epinfo; // Temporary assign new pointer to epInfo to p->epinfo in order to avoid toggle inconsistence p->epinfo = epInfo; p->lowspeed = lowspeed; // Get device descriptor rcode = pUsb->getDevDescr(0, 0, sizeof (USB_DEVICE_DESCRIPTOR), buf); // Restore p->epinfo p->epinfo = oldep_ptr; if(rcode) goto FailGetDevDescr; // if(udd->idVendor != CH34x_VENDOR_ID || udd->idProduct != CH340_PRODUCT_ID || udd->idProduct != CH341_PRODUCT_ID) if(!VIDPIDOK(udd->idVendor, udd->idProduct)) { USBTRACE("CH34X Init: Product not supported\r\n"); USBTRACE2("Expected VID:", CH34x_VENDOR_ID); USBTRACE2("Found VID:", udd->idVendor); USBTRACE2("Expected PID[0]:", CH340_PRODUCT_ID); USBTRACE2("Expected PID[1]:", CH341_PRODUCT_ID); USBTRACE2("Found PID:", udd->idProduct); return USB_DEV_CONFIG_ERROR_DEVICE_NOT_SUPPORTED; } // Allocate new address according to device class bAddress = addrPool.AllocAddress(parent, false, port); if(!bAddress) return USB_ERROR_OUT_OF_ADDRESS_SPACE_IN_POOL; // Extract Max Packet Size from the device descriptor epInfo[0].maxPktSize = udd->bMaxPacketSize0; // Assign new address to the device rcode = pUsb->setAddr(0, 0, bAddress); if(rcode) { p->lowspeed = false; addrPool.FreeAddress(bAddress); bAddress = 0; USBTRACE2("setAddr:", rcode); return rcode; } USBTRACE2("Addr:", bAddress); p->lowspeed = false; p = addrPool.GetUsbDevicePtr(bAddress); if(!p) return USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL; p->lowspeed = lowspeed; num_of_conf = udd->bNumConfigurations; // Assign epInfo to epinfo pointer rcode = pUsb->setEpInfoEntry(bAddress, 1, epInfo); if(rcode) goto FailSetDevTblEntry; USBTRACE2("NC:", num_of_conf); for(uint8_t i = 0; i < num_of_conf; i++) { HexDumper<USBReadParser, uint16_t, uint16_t> HexDump; // ConfigDescParser < 0xFF, 0xFF, 0xFF, CP_MASK_COMPARE_ALL> confDescrParser(this); ConfigDescParser < 0xFF, 0x01, 0x02, CP_MASK_COMPARE_ALL> confDescrParser(this); rcode = pUsb->getConfDescr(bAddress, 0, i, &HexDump); if(rcode) goto FailGetConfDescr; rcode = pUsb->getConfDescr(bAddress, 0, i, &confDescrParser); if(rcode) goto FailGetConfDescr; if(bNumEP > 1) break; } // for if(bNumEP < 2) return USB_DEV_CONFIG_ERROR_DEVICE_NOT_SUPPORTED; USBTRACE2("NumEP:", bNumEP); // Assign epInfo to epinfo pointer rcode = pUsb->setEpInfoEntry(bAddress, bNumEP, epInfo); USBTRACE2("Conf:", bConfNum); // Set Configuration Value rcode = pUsb->setConf(bAddress, 0, bConfNum); if(rcode) goto FailSetConfDescr; VendorRead( VENDOR_VERSION, 0x0000, 0x0000, buf, 0x02 ); VendorWrite( VENDOR_SERIAL_INIT, 0x0000, 0x0000, NULL, 0x00 ); VendorWrite( VENDOR_WRITE, 0x1312, 0xD982, NULL, 0x00 ); VendorWrite( VENDOR_WRITE, 0x0F2C, 0x0004, NULL, 0x00 ); VendorRead( VENDOR_READ, 0x2518, 0x0000, buf, 0x02 ); VendorWrite( VENDOR_WRITE, 0x2727, 0x0000, NULL, 0x00 ); VendorWrite( VENDOR_MODEM_OUT, 0x009F, 0x0000, NULL, 0x00 ); rcode = pAsync->OnInit(this); if(rcode) goto FailOnInit; USBTRACE("CH34X configured\r\n"); ready = true; bPollEnable = true; return 0; FailGetDevDescr: #ifdef DEBUG_USB_HOST NotifyFailGetDevDescr(); goto Fail; #endif FailSetDevTblEntry: #ifdef DEBUG_USB_HOST NotifyFailSetDevTblEntry(); goto Fail; #endif FailGetConfDescr: #ifdef DEBUG_USB_HOST NotifyFailGetConfDescr(); goto Fail; #endif FailSetConfDescr: #ifdef DEBUG_USB_HOST NotifyFailSetConfDescr(); goto Fail; #endif FailOnInit: #ifdef DEBUG_USB_HOST USBTRACE("OnInit:"); Fail: NotifyFail(rcode); #endif Release(); return rcode; } void CH34X::EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *pep) { ErrorMessage<uint8_t > (PSTR("Conf.Val"), conf); ErrorMessage<uint8_t > (PSTR("Iface Num"), iface); ErrorMessage<uint8_t > (PSTR("Alt.Set"), alt); bConfNum = conf; uint8_t index; if((pep->bmAttributes & 0x03) == 3 && (pep->bEndpointAddress & 0x80) == 0x80) index = epInterruptInIndex; else if((pep->bmAttributes & 0x02) == 2) index = ((pep->bEndpointAddress & 0x80) == 0x80) ? epDataInIndex : epDataOutIndex; else return; // Fill in the endpoint info structure epInfo[index].epAddr = (pep->bEndpointAddress & 0x0F); epInfo[index].maxPktSize = (uint8_t)pep->wMaxPacketSize; epInfo[index].bmSndToggle = 0; epInfo[index].bmRcvToggle = 0; bNumEP++; PrintEndpointDescriptor(pep); } uint8_t CH34X::Release() { pUsb->GetAddressPool().FreeAddress(bAddress); bAddress = 0; bNumEP = 1; qNextPollTime = 0; bPollEnable = false; ready = false; return pAsync->OnRelease(this); } uint8_t CH34X::Poll() { uint8_t rcode = 0; //if (!bPollEnable) // return 0; //if (qNextPollTime <= millis()) //{ // USB_HOST_SERIAL.println(bAddress, HEX); // qNextPollTime = millis() + 100; //} return rcode; } int CH34X::VendorRead(uint8_t request, uint16_t value, uint16_t index, uint8_t *buf, uint16_t len) { int retval; retval = pUsb->ctrlReq( bAddress, //addr 0, //ep bmREQ_CH34X_IN, //bmReqType request, //bRequest value & 0xff, //wValLo value >> 8, //wValHi index, //wInd len, //total len, //nbytes buf, //dataptr NULL); //p return retval; } int CH34X::VendorWrite(uint8_t request, uint16_t value, uint16_t index, uint8_t *buf, uint16_t len){ int retval; retval = pUsb->ctrlReq( bAddress, //addr 0, //ep bmREQ_CH34X_OUT, //bmReqType request, //bRequest value & 0xff, //wValLo value >> 8, //wValHi index, //wInd len, //total len, //nbytes buf, //dataptr NULL); //p return retval; } int CH34X::SetControlLine(uint8_t value){ int retval; retval = VendorWrite( VENDOR_MODEM_OUT, (unsigned short)value, 0x0000, NULL, 0x00 ); return retval; } uint8_t CH34X::SetLineCoding(const LINE_CODING *coding) { uint8_t retval; uint8_t divisor = 0; uint8_t reg_count = 0; uint8_t factor = 0; uint8_t reg_value = 0; uint16_t value = 0; uint16_t index = 0; //Data bits(5-8) switch (coding->bDataBits) { case 5: reg_value |= 0x00; break; case 6: reg_value |= 0x01; break; case 7: reg_value |= 0x02; break; case 8: reg_value |= 0x03; break; default: reg_value |= 0x03; break; } //Stop bit("1.5 stop bits" is not supported?) switch(coding->bCharFormat){ case 0: //1 stop bit reg_value |= 0x00; break; case 2: //2 stop bits reg_value |= 0x04; break; default: reg_value |= 0x00; break; } //Parity(Mark,Space is not supported?) switch (coding->bParityType) { case 0: //None reg_value |= 0x00; break; case 1: //Odd reg_value |= (0x08 | 0x00); break; case 2: //Even reg_value |= (0x08 | 0x10); break; default: reg_value |= 0x00; break; } //Determine the baud rate CalcBaudRate(coding->dwDTERate, &factor, &divisor); //enable SFR_UART RX and TX reg_value |= 0xc0; //enable SFR_UART Control register and timer reg_count |= 0x9c; value |= reg_count; value |= (uint16_t)reg_value << 8; index |= 0x80 | divisor; index |= (uint16_t)factor << 8; VendorWrite( VENDOR_SERIAL_INIT, value, index, NULL, 0 ); //change control lines? SetControlLine(0x00); //Flow control if(coding->bFlowControl == 1){ VendorWrite( VENDOR_WRITE, 0x2727, 0x0101, NULL, 0); } return ( retval ); } int CH34X::CalcBaudRate(uint32_t baud_rate, uint8_t *a, uint8_t *b){ unsigned long factor = 0; short divisor = 0; if( !baud_rate ) return -1; factor = (CH34x_BAUDRATE_FACTOR / baud_rate); divisor = CH34x_BAUDRATE_DIVMAX; while( (factor > 0xfff0) && divisor ) { factor >>= 3; divisor --; } if( factor > 0xfff0 ) return -1; factor = 0x10000 - factor; *a = (factor & 0xff00) >> 8; *b = divisor; USBTRACE2("factor:", *a); USBTRACE2("divisor:", *b); return 0; } uint8_t CH34X::RcvData(uint16_t *bytes_rcvd, uint8_t *dataptr) { return pUsb->inTransfer(bAddress, epInfo[epDataInIndex].epAddr, bytes_rcvd, dataptr); } uint8_t CH34X::SndData(uint16_t nbytes, uint8_t *dataptr) { return pUsb->outTransfer(bAddress, epInfo[epDataOutIndex].epAddr, nbytes, dataptr); } void CH34X::PrintEndpointDescriptor(const USB_ENDPOINT_DESCRIPTOR* ep_ptr) { Notify(PSTR("Endpoint descriptor:"), 0x80); Notify(PSTR("\r\nLength:\t\t"), 0x80); D_PrintHex<uint8_t > (ep_ptr->bLength, 0x80); Notify(PSTR("\r\nType:\t\t"), 0x80); D_PrintHex<uint8_t > (ep_ptr->bDescriptorType, 0x80); Notify(PSTR("\r\nAddress:\t"), 0x80); D_PrintHex<uint8_t > (ep_ptr->bEndpointAddress, 0x80); Notify(PSTR("\r\nAttributes:\t"), 0x80); D_PrintHex<uint8_t > (ep_ptr->bmAttributes, 0x80); Notify(PSTR("\r\nMaxPktSize:\t"), 0x80); D_PrintHex<uint16_t > (ep_ptr->wMaxPacketSize, 0x80); Notify(PSTR("\r\nPoll Intrv:\t"), 0x80); D_PrintHex<uint8_t > (ep_ptr->bInterval, 0x80); Notify(PSTR("\r\n"), 0x80); }
cdc_ch34x.h
#if !defined(__CDC_CH34X_H__) #define __CDC_CH34X_H__ #include "Usb.h" #define bmREQ_CH34X_OUT 0x40 #define bmREQ_CH34X_IN 0xc0 //#define bmREQ_CH34X_OUT USB_SETUP_HOST_TO_DEVICE|USB_SETUP_TYPE_VENDOR|USB_SETUP_RECIPIENT_INTERFACE //#define bmREQ_CH34X_IN USB_SETUP_DEVICE_TO_HOST|USB_SETUP_TYPE_VENDOR|USB_SETUP_RECIPIENT_INTERFACE //----------------------------------------------------- #define CH34x_VENDOR_ID 0x1A86 #define CH340_PRODUCT_ID 0x7523 #define CH341_PRODUCT_ID 0x5523 #define CH34x_CLOSING_WAIT (30 * HZ) #define CH34x_BUF_SIZE 1024 #define CH34x_TMP_BUF_SIZE 1024 //Vendor define #define VENDOR_WRITE_TYPE 0x40 #define VENDOR_READ_TYPE 0xC0 #define VENDOR_READ 0x95 #define VENDOR_WRITE 0x9A #define VENDOR_SERIAL_INIT 0xA1 #define VENDOR_MODEM_OUT 0xA4 #define VENDOR_VERSION 0x5F //For CMD 0xA4 #define UART_CTS 0x01 #define UART_DSR 0x02 #define UART_RING 0x04 #define UART_DCD 0x08 #define CONTROL_OUT 0x10 #define CONTROL_DTR 0x20 #define CONTROL_RTS 0x40 //Uart state #define UART_STATE 0x00 #define UART_OVERRUN_ERROR 0x01 #define UART_BREAK_ERROR //no define #define UART_PARITY_ERROR 0x02 #define UART_FRAME_ERROR 0x06 #define UART_RECV_ERROR 0x02 #define UART_STATE_TRANSIENT_MASK 0x07 //Port state #define PORTA_STATE 0x01 #define PORTB_STATE 0x02 #define PORTC_STATE 0x03 //CH34x Baud Rate #define CH34x_BAUDRATE_FACTOR 1532620800 #define CH34x_BAUDRATE_DIVMAX 3 //----------------------------------------------------- typedef struct { uint32_t dwDTERate; // Data Terminal Rate in bits per second uint8_t bCharFormat; // 0 - 1 stop bit, 1 - 1.5 stop bits, 2 - 2 stop bits uint8_t bParityType; // 0 - None, 1 - Odd, 2 - Even, 3 - Mark, 4 - Space uint8_t bDataBits; // Data bits (5, 6, 7, 8 or 16) uint8_t bFlowControl; // 0 - Software, 1 - Hardware } LINE_CODING; class CH34X; class CDCAsyncOper { public: virtual uint8_t OnInit(CH34X *pch34x) { return 0; }; virtual uint8_t OnRelease(CH34X *pch34x) { return 0; }; }; #define CH34X_MAX_ENDPOINTS 4 class CH34X : public USBDeviceConfig, public UsbConfigXtracter { static const uint8_t epDataInIndex; // DataIn endpoint index static const uint8_t epDataOutIndex; // DataOUT endpoint index static const uint8_t epInterruptInIndex; // InterruptIN endpoint index CDCAsyncOper *pAsync; USB *pUsb; uint8_t bAddress; uint8_t bConfNum; // configuration number uint8_t bNumIface; // number of interfaces in the configuration uint8_t bNumEP; // total number of EP in the configuration uint32_t qNextPollTime; // next poll time bool bPollEnable; // poll enable flag bool ready; EpInfo epInfo[CH34X_MAX_ENDPOINTS]; void PrintEndpointDescriptor(const USB_ENDPOINT_DESCRIPTOR* ep_ptr); int VendorRead(uint8_t request, uint16_t value, uint16_t index, uint8_t *buf, uint16_t len); int VendorWrite(uint8_t request, uint16_t value, uint16_t index, uint8_t *buf, uint16_t len); int SetControlLine(uint8_t value); int CalcBaudRate(uint32_t baud_rate, uint8_t *a, uint8_t *b); public: CH34X(USB *pusb, CDCAsyncOper *pasync); uint8_t SetLineCoding(const LINE_CODING *dataptr); // Methods for recieving and sending data uint8_t RcvData(uint16_t *bytes_rcvd, uint8_t *dataptr); uint8_t SndData(uint16_t nbytes, uint8_t *dataptr); // USBDeviceConfig implementation uint8_t Init(uint8_t parent, uint8_t port, bool lowspeed); uint8_t Release(); uint8_t Poll(); virtual uint8_t GetAddress() { return bAddress; }; virtual bool isReady() { return ready; } // UsbConfigXtracter implementation void EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *ep); virtual bool VIDPIDOK(uint16_t vid, uint16_t pid) { return (vid == CH34x_VENDOR_ID && (pid == CH340_PRODUCT_ID || pid == CH341_PRODUCT_ID)); } }; #endif // __CDC_CH34X_H__
動作確認用スケッチ
今回は動作確認用にArduinoを2台使用しています。1台はUSB-HOSTとなるArduino uno+Arduino USB Host Shieldです。こちらは動作確認のために次項のスケッチを書き込みます。もう一台はUSB-PeripheralとなるArduino nanoで、これに搭載されているCH340とシリアル通信を行います。こちらにはArduino付属のサンプルスケッチを書き込みました。
Host側スケッチ
以下の処理を行うスケッチです。
- CH340との接続
- 相手側に適当なデータ(0x01)を送信
- 相手側から送られたデータを受信
- 受信したデータを表示
#include <cdc_ch34x.h> #include <usbhub.h> #include "pgmstrings.h" // Satisfy the IDE, which needs to see the include statment in the ino too. #ifdef dobogusinclude #include <spi4teensy3.h> #include <SPI.h> #endif class CH34XAsyncOper : public CDCAsyncOper { public: uint8_t OnInit(CH34X *pch34x); }; uint8_t CH34XAsyncOper::OnInit(CH34X *pch34x) { uint8_t rcode; LINE_CODING lc; lc.dwDTERate = 115200; lc.bCharFormat = 0; lc.bParityType = 0; lc.bDataBits = 8; lc.bFlowControl = 0; rcode = pch34x->SetLineCoding(&lc); if (rcode) ErrorMessage<uint8_t>(PSTR("SetLineCoding"), rcode); return rcode; } USB Usb; USBHub Hub1(&Usb); USBHub Hub2(&Usb); CH34XAsyncOper AsyncOper; CH34X Ch34x(&Usb, &AsyncOper); uint32_t next_time; // forDebug static FILE uartout; static int uart_putchar(char c, FILE *stream) { if(Serial.write(c) > 0) { return 0; } else { return -1; } } void setup() { //forDebug fdev_setup_stream(&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE); stdout = &uartout; Serial.begin( 115200 ); #if !defined(__MIPSEL__) while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection #endif Serial.println("Start"); if (Usb.Init() == -1) Serial.println("OSC did not start."); delay( 200 ); next_time = millis() + 10000; } void loop() { uint8_t buf[256]; uint8_t retval; Usb.Task(); if ( Usb.getUsbTaskState() == USB_STATE_RUNNING ) { if ((long)(millis() - next_time) >= 0L) { while ( 1 ){ if(Ch34x.isReady()){ Serial.println("CH34X:Ready!"); memset(buf,0x00,sizeof(buf)); buf[0] = 0x01; retval = Ch34x.SndData(1, buf); Serial.print("SndData:"); Serial.print(retval); uint16_t size = sizeof(buf); retval = Ch34x.RcvData(&size, buf); Serial.print("\r\nRcvData:"); Serial.print(retval); Serial.print("\r\nSize:"); Serial.print(size); if(size > 0){ Serial.println("\r\nData received:"); printf("%s\n", buf); } }else{ Serial.println("CH34X:not ready."); } delay(1000); }; //stop } } }
Peripheral側スケッチ
Peripheral側には以下のサンプルスケッチを使用しました。ホスト側から何か適当なデータを送信するとADCの取得値を送り返す処理です。
- ArduinoIDEメニューバー > ファイル > スケッチ例 > 04.Communication > SerialCallResponseASCII
動作確認
USB-HUB経由でArduino nanoを接続して動作確認したところ、以下のように問題なくデータ送受信を行うことが出来ました。
ほとんど移植するだけで上手く動いてくれましたが、USB-HostとPeripheralを直接繋いだ時は問題なく動作するのにUSB-HUBを経由すると上手く動かなくなったりして少し手間取りました(ハブを変えると動きが変わったりします)。データ受信時にNakが返ってきた場合に何回かリトライするようにしてやると上手く動くようになりました。これがベストなのかは分かりませんが、取り合えずは動いています。
ちなみに、今のところ分かっている制約次項としては以下のものがあります。ストップビット/パリティビットについてはほぼ問題ないと思いますが、USBデタッチが上手くいかないのはそのうち修正したいですね。
- USBデバイスを一度切断すると、次はH/Wリセットしないかぎり再認識しない(USBデタッチ処理が上手くいかない?)
- ストップビット"1.5"は設定不可(Linuxドライバの中に設定が無い)
- パリティビット"Mark","Space"は設定不可(Linuxドライバの中に設定が無い)
参考
この記事は以下の内容を参考に記載させて頂きました。
- WCH - CH341 LINUX SOURCE CODE
http://www.wch.cn/download/CH341SER_LINUX_ZIP.html - SyncHack - USB/Class/Class番号の詳細
http://mcn.oops.jp/wiki/index.php?USB%2FClass%2FClass%C8%D6%B9%E6%A4%CE%BE%DC%BA%D9 - Linux工作室 - libusbについて
http://penguin.tantin.jp/hard/libusb%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6.html#gca505bb - とりメモ - 9.3 USB Device Request
https://sites.google.com/site/toriaezunomemo/home/usb2-0memo/usb-device-framework/9-3-usb-device-request - JM プロジェクト - TERMIOS
https://linuxjm.osdn.jp/html/LDP_man-pages/man3/termios.3.html - 滴了庵日録 - termios構造体のc_cflagについて
http://d.hatena.ne.jp/licheng/20140515/p2 - 程序园 - Android USB转串口编程
http://www.voidcn.com/blog/Ever_GZ/article/p-4859973.html