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つのデバイスで仮想的に(ソフト的に)複数のデバイスが存在するということもあるため、デバイスの型とソフト的に見える型の不一致などが頻繁に起きます。
ターミナルによる通信
シリアルポートの設定を変更したり、任意の速度で通信するためにはターミナルや設定ソフトが必要ですが、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として認識しない原因
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) },
0xffff を 0x5ff に書き換えることで、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バイナリのみの提供のため利用不可。