2007/04/05(木)PL2303X + Linuxで高速通信はできるか

FT232RLはとりあえず諦めて、PL-2303Xのドライバの動作を検討しました。

パッチを当ててインストール

Linuxソースの drivers/usb/serial/pl2303.c がデバイスドライバです。カーネルを再構築するのは大変なので、FT232のデバイスドライバから Makefile と Rules.make を拝借し、適当に書き換えてカーネルモジュールを作り増した。

1Mbpsは対応していませんので、ドライバをごにょごにょと書き換えます。

baud = 0;
switch (cflag & CBAUD) {
	case B0:	baud = 0;	break;
	case B75:	baud = 75;	break;
	case B150:	baud = 150;	break;
	case B300:	baud = 300;	break;
	case B600:	baud = 600;	break;
	case B1200:	baud = 1200;	break;
	case B1800:	baud = 1800;	break;
	case B2400:	baud = 2400;	break;
	case B4800:	baud = 4800;	break;
	case B9600:	baud = 9600;	break;
	case B19200:	baud = 19200;	break;
	case B38400:	baud = 38400;	break;
	case B57600:	baud = 57600;	break;
	case B115200:	baud = 115200;	break;
	case B230400:	baud = 230400;	break;
	case B460800:	baud = 460800;	break;
	default:
		err ("pl2303 driver does not support the baudrate requested (fix it)");
		break;
}
baud = 1000000;
dbg("%s - baud = %d", __FUNCTION__, baud);
if (baud) {
	buf[0] = baud & 0xff;
	buf[1] = (baud >> 8) & 0xff;
	buf[2] = (baud >> 16) & 0xff;
	buf[3] = (baud >> 24) & 0xff;
}
<中略>
i = usb_control_msg (serial->dev, usb_sndctrlpipe (serial->dev, 0),
		     SET_LINE_REQUEST, SET_LINE_REQUEST_TYPE, 
		     0, 0, buf, 7, 100);

ソースをみれば分かりますが、このデバイスはかなり変わっていて、通信速度(Baud rate)を直接デバイスに送る(設定する)仕組みになっています(普通は分周カウンタの値を送る)。

さてこれで通信……としてみたものの、データがすべて化けてしまって全くだめでした。100Kbpsでもダメでした。これは通信速度が合っていないときに起こる症状です。どうやら、うまく分周されていないようなのですが、データシートには 6Mbps まで対応と書かれています。

原因

ここでみつけたPL-2303の資料(PDF)(Xなし)によれば(表3の少し手前)、

The programmable baud rate generator supports baud rates up to 1.2M bps

とのことで、どうやらドライバが PL-2303X に対応しておらず、PL-2303互換デバイスとして使用しているためではないかと思うのですが。つまり、PL-2303Xのデバイスドライバをみつければよいのではないかと……。

訂正。PL-2303X資料(pdf)によれば、高速な baud として設定可能な値は

921600, 1228800, 2457600, 3000000, 6000000

だそうです。微妙に使えない PL-2303X。

2007/04/02(月)FT232RL を Linux kernel 2.4.17 で認識させる

FTDI製FT232RL(FT232R)とは、3Mbpsまでの速度に対応した高速USBシリアルチップ(Serial Conveter)です。USBに接続するだけで、TTLレベルで高速シリアル通信が実現できます。

デバイスドライバの組み込み

ドライバ配布サイトでWindows用、Mac用、Linux用のドライバが配布されています。Linux kernel 2.4.20以降には標準でドライバが組み込まれていますが、2.4.17には組み込まれていません。

実験中ドライバの利用

Kernel 2.4.17にはFTDI USB Serialのドライバが実験中として組み込まれています。

USB support
 -> USB Serial Converter support
   -> <*> USB FTDI Single Port Serial Driver (EXPERIMENTAL)
usbserial.c: FTDI 8U232AM converter detected
usbserial.c: FTDI 8U232AM converter now attached to ttyUSB0 (or usb/tts/0 for devfs)

カーネルにこれを組み込むことで一応認識はするのですが、次の問題が起きました。

  • 9600, 19200, 38400, 78600……921600(最大)といった系列しか指定できない。*1
  • 試しに460800に設定したところ、使用したマシンが非力なのかKernelごと落ちた(応答しなくなった)

公式配布ドライバの使用

配布されているドライバソース ftdi_sio.tar.gz を展開し make します。make するためにはカーネルのソース(の一部、ヘッダ類)が必要です。

kernelソースを展開し適切にパスを設定後 make してみると、コンパイルエラーが発生しました。どうやらkernelが古いせいか

	.owner =		THIS_MODULE,

という項目が存在しない*2のが原因のようです。これらの行をすべて削除(またはコメントアウト)することでコンパイルできるようになります。make が無事成功すると、ftdi_sio.oというファイルができます。これがドライバ本体(カーネルモジュール)です。

このドライバをカーネルに組み込むためには root で次のようにコマンドを実行します。

組み込み
# insmod ftdi_sio.o
Kernelから削除
# rmmod ftdi_sio.o
組み込み済カーネルモジュールの確認
# lsmod

組み込んでみたのですが、エラーが出てしまい、ドライバが動作しません。

ftdi_sio.c: v1.3.5r1:USB FTDI Serial Converters Driver
hub.c: USB new device connect on bus1/1, assigned device number 3
usbserial.c: descriptors matched, but endpoints did not
usb.c: USB device 3 (vend/prod 0x403/0x6001) is not claimed by any active driver.

0x403/0x6001は、FTDIの8U232AMを示し、ドライバソースには実際その記述があるのですが、「but endpoints did not」と出て認識しません。endpoints とは何であるかということですが、調べてみるとUSBデバイスがホスト側と通信する際に利用するバッファのことのようです。

要するにエンドポイントの設定がおかしいと言っているのですが、ドライバのソースにはきちんと記述されています。

static struct usb_serial_device_type ftdi_8U232AM_device = {
/*	.owner =		THIS_MODULE, */
	.name =			"FTDI 8U232AM Compatible",
	.id_table =		id_table_8U232AM,
	.num_interrupt_in =	0,
	.num_bulk_in =		1,
	.num_bulk_out =		1,

usbserial.c をみてみると

/* verify that we found all of the endpoints that we need */
if (!((interrupt_pipe & type->needs_interrupt_in) &&
      (bulk_in_pipe & type->needs_bulk_in) &&
      (bulk_out_pipe & type->needs_bulk_out))) {
	/* nope, they don't match what we expected */
	info("descriptors matched, but endpoints did not");
	return NULL;
}

ということで、この項目が設定されてないことが原因のようです。

公式配布ドライバへのパッチ

ftdi_sio.c の中に記述されているすべてのデバイス情報(8U232AM, FT232BM等々)について、次のような書き換えを行います。

static struct usb_serial_device_type ftdi_8U232AM_device = {
/*	.owner =		THIS_MODULE,	*/
	.name =			"FTDI 8U232AM Compatible",
	.id_table =		id_table_8U232AM,
	.needs_interrupt_in =	DONT_CARE,
	.needs_bulk_in =	MUST_HAVE,
	.needs_bulk_out =	MUST_HAVE,
	.num_interrupt_in =	0,

色が変わっているところが追加部分です。これをコンパイルしてカーネルモジュールを作成しインストールすると、

usbserial.c: FTDI FT232BM converter detected
usbserial.c: FTDI FT232BM converter now attached to ttyUSB0 (or usb/tts/0 for devfs)

として認識されます。

FT232RLと表示されませんが仕様です。コンピューターデバイスではよくあることで、デバイスとして異なってもソフトウェア的に同一の場合は、同じプロダクトIDを持つことがあります。また1つのデバイスで仮想的に(ソフト的に)複数のデバイスが存在するということもあるため、デバイスの型とソフト的に見える型の不一致などが頻繁に起きます。

*1 : あたらしいドライバならば任意の速度が設定出来ることはソースより確認済

*2 : struct usb_serial_device_type

ターミナルによる通信 2007/04/03

シリアルポートの設定を変更したり、任意の速度で通信するためにはターミナルや設定ソフトが必要ですが、minicom や stty などはお節介なことに"9600, 19200……"系列しか指定できません。kermit もダメでした。

いろいろ調べたところ、cu というコマンドで指定できました。cu コマンドは uucp と一緒に配布されています(リンク先、左下のソースから uucp_1.07.orig.tar.gz をダウンロード)。

# tar zxvf uucp_1.07.orig.tar.gz
# cd uucp-1.07
# ./configure
# make
# make install
# mkdir /usr/spool
# mkdir /usr/spool/uucp
# chown uucp /usr/spool/uucp

利用方法は次のようになります。

# cu -l /dev/ttyUSB0 -s 38400

1Mbps通信はできるか?

早速

# cu -l /dev/ttyUSB0 -s 1000000 -d
cu: fconn_open: Opening port /dev/ttyUSB0 (speed 1000000)
cu: fconn_set: Changing setting to 1, 2, 2

と設定してみます(-d はデバッグフラグです)。ところが、公式ドライバでも、2.4.17付属ドライバでも通信速度が変更できないようです。ドライバソースを改造してデバッグメッセージを出力させてみると、

ftdi_sio.c: Set to 9600

などと出てしまいます。カーネルの段階(tty?)で通信速度が書き換えられている模様です(詳細不明)。

仕方がないので、ドライバのソース(ftdi_sio.c)を書き換えて、無理矢理 1Mbps 通信をさせてみました。

  • Linux 2.4.17付属ドライバ …… 99%程度取りこぼし、使い物にならない
  • 公式配布ドライバ …… 通信速度の設定によらず、1byteも受信できない

以上うまく通信できませんでした。Linux 2.4.17のドライバは未成熟である可能性は捨てきれず、逆に公式配布ドライバはカーネルが古すぎるために(非互換により)通信すら行えていない可能性が高いと思います。

Linux 2.4.17のドライバを改変し受信したデータ

00001,00001,00001,00001,00001,00001,00001,00001,00001,00001

00002,00002,00002,00002,00002,00002,00002,00002,00002,00002

00003,00003,00003,00003,00003,00003,00003,00003,00003,00003

00004,00004,00004,00004,00004,00004,00004,00004,00004,00004

00005,00005,00005,00005,00005,00005,00005,00005,00005,00005

00006,00006,00006,00006,00006,00006,00006,00006,00006,00006

00007,0000720,359205921,35921,35921,35921,35921,35921,35921,35921

35922,35922,35935922,35922

35923,35923,35923,35923,35923,35923,35923,35923,3535924,35924,35924,35924,35924,35924

35925,35925,35925,35925,35

35926,35926,35926,35926,35926,35926,35926,359

FT232Rとして認識しない原因 2007/04/04

ftdi_sio.h では

#define FTDI_8U232AM_PID 0x6001 /* Similar device to SIO above */

となっていて、いくつかの製品で同じプロダクトID(PID)を持っています。複数の製品をどうやって見分けているのかなと思ったのですが、productversion という2バイトの値で識別しているようです。

USB_DEVICE_VER (vendorId, productId, lo, hi)
	... like USB_DEVICE with lo <= productversion <= hi
<略>
static struct usb_device_id id_table_8U232AM [] = {
	{ USB_DEVICE_VER(FTDI_VID, FTDI_IRTRANS_PID, 0, 0x3ff) },
	{ USB_DEVICE_VER(FTDI_VID, FTDI_8U232AM_PID, 0, 0x3ff) },
<略>
static struct usb_device_id id_table_FT232BM [] = {
	{ USB_DEVICE_VER(FTDI_VID, FTDI_IRTRANS_PID, 0x400, 0xffff) },
	{ USB_DEVICE_VER(FTDI_VID, FTDI_8U232AM_PID, 0x400, 0xffff) },
<略>
static struct usb_device_id id_table_FT232R[] = {
	{ USB_DEVICE_VER(FTDI_VID, FTDI_8U232AM_PID, 0x600, 0xffff) },

0xffff0x5ff に書き換えることで、FT232R と認識しますが、特に挙動は変わりませんでした。usb_serial_device_type をみても表示文字列以外、内部処理に特に違いはないようです。

その後の検討とまとめ

  • 公式ドライバの ftdi_read_bulk_callback() 内の "port->open_count" の条件文を無効化(常に真)することでデータ通信はできましたが、Linux 2.4.17ドライバ同様に99%取りこぼしでした。
  • 試しに usbserial.c を別バージョンのカーネルから引っ張ってきましたが、案の定コンパイルすらできませんでした。
  • Linux 2.4.17で 100kbps で受信させてみましたが、それでも多少取りこぼしが発生しました。
  • Linux 2.6.17カーネルで試したところ、何の問題*3もなく動作しました。

    Linux version 2.6.17
    [17179838.796000] usb 1-1: configuration #1 chosen from 1 choice
    [17179838.800000] ftdi_sio 1-1:1.0: FTDI USB Serial Device converter detected
    [17179838.800000] drivers/usb/serial/ftdi_sio.c: Detected FT232BM
    [17179838.800000] usb 1-1: FTDI USB Serial Device converter now attached to ttyUSB0
    
    # stty -F /dev/ttyUSB0 1000000
    # cat /dev/ttyUSB0
    

Linux 2.4.17時代にはおそらく高速シリアル通信は考えられておらず、そのためまともに通信できないのではないかと思います。

もっとも、使用したマシンが非力すぎるという可能性も捨てきれない面はありますが、PowerPCベース200MHz程度あれば、いくら何でも100KB/sぐらい受信してよさそうですので(LANは3Mbps以上でてますし)。

残された道

  • Linux 2.4.17カーネルのドライバを適当に最新カーネルのものに入れ替える。
  • Linux 2.4.17カーネルのドライバを元に地道にパッチを当てる。
  • Linux 2.4.34等の最新カーネルをベースに、特殊ハード(組み込み)向けの改造を加える。
  • VCPドライバではなく、D2XXドライバの利用を検討する。x86バイナリのみの提供のため利用不可。

*3 : 取りこぼしもおそらくなし

OK キャンセル 確認 その他