[Arduino] USB Host ShieldとUSBシリアル通信を行う(CH340編)

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
Arduino unoとUSB Host Shield
Arduino uno + USB Host Shield

 

Arduino nano互換品
Arduino nano互換ボード(200円くらい)

 

開発環境

  • 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
platformio_ch340.png
最近はPlatformIOを使っています。コード補完してくれるので本家IDEより気に入っています。

CH340のディスクリプタテーブルを確認する

冒頭でも述べましたが、今回対象としているUSBシリアル変換ICはCH340です。安価なArduinoクローンなどのマイコンボードでよく見かけるチップで、Windows7で使う場合でも別途ドライバをインストールする必要がありました。これをArduino + USB Host Shieldと通信させたいわけです。

Arduino nanoとCH340
Arduino nano互換ボードを裏返してみるとCH340が載っているのが分かります

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に移植してやれば動きそうです。

ドライバは既存の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側スケッチ

以下の処理を行うスケッチです。

  1. CH340との接続
  2. 相手側に適当なデータ(0x01)を送信
  3. 相手側から送られたデータを受信
  4. 受信したデータを表示

#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
ArduinoIDEサンプルスケッチ
ArduinoIDEのこの箇所にあるスケッチをそのまま使いました

動作確認

USB-HUB経由でArduino nanoを接続して動作確認したところ、以下のように問題なくデータ送受信を行うことが出来ました。

Arduino USB HostとCH340
動作確認中
PlatformIO Serialコンソール
シリアルコンソールを使ってPeripheral側とのデータ送受信が確認出来ました

ほとんど移植するだけで上手く動いてくれましたが、USB-HostとPeripheralを直接繋いだ時は問題なく動作するのにUSB-HUBを経由すると上手く動かなくなったりして少し手間取りました(ハブを変えると動きが変わったりします)。データ受信時にNakが返ってきた場合に何回かリトライするようにしてやると上手く動くようになりました。これがベストなのかは分かりませんが、取り合えずは動いています。

USB-HUB
このUSB-HUBを使うと受信時にNak応答が返ってきてデータ受信出来ませんでした。最終的にはNak時にリトライすることで回避しています。

ちなみに、今のところ分かっている制約次項としては以下のものがあります。ストップビット/パリティビットについてはほぼ問題ないと思いますが、USBデタッチが上手くいかないのはそのうち修正したいですね。

  • USBデバイスを一度切断すると、次はH/Wリセットしないかぎり再認識しない(USBデタッチ処理が上手くいかない?)
  • ストップビット”1.5″は設定不可(Linuxドライバの中に設定が無い)
  • パリティビット”Mark”,”Space”は設定不可(Linuxドライバの中に設定が無い)

 

参考

この記事は以下の内容を参考に記載させて頂きました。

コメントを残す

メールアドレスが公開されることはありません。

This blog is kept spam free by WP-SpamFree.