= [http://www.ftdichip.com/Products/ICs/FT260.html FTDI FT260]で実験 =
FT260をBBBに繋いで使い方の実験してみる。
== パッケージインストール ==
始めにpythonで実験するために、hidapi周りのパッケージをインストールする。
{{{
root@beaglebone:~# apt-get install libhidapi-libusb0 libudev-dev libusb
root@beaglebone:~# pip install --trusted-host pypi.python.org Cython hidapi
}}}
== デバイスの接続 ==
開発ボードにはI2C接続のEEPROMが搭載されていて、defaultではジャンパで接続されている。[[br]]
なるべく余計な接続をしない状態で実験したかったのでEEPROMなしの状態にジャンパを設定する。[[br]]
デバイスを接続すると、{{{musb-hdrc musb-hdrc.1.auto: Babble}}}というメッセージが大量に出力されるが、一応認識されて繋がりはするようだ。
{{{
[ 6891.320644] musb-hdrc: 28/31 max ep, 16384/16384 memory
[ 6891.659553] musb-hdrc musb-hdrc.1.auto: Babble
[ 6891.664416] musb-hdrc musb-hdrc.1.auto: Babble
[ 6891.668943] musb-hdrc: setup fifo_mode 4
[ 6891.668996] musb-hdrc: 28/31 max ep, 16384/16384 memory
[ 6892.066437] usb 1-1: new full-speed USB device number 27 using musb-hdrc
[ 6892.199679] usb 1-1: New USB device found, idVendor=0403, idProduct=6030
[ 6892.199741] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 6892.199773] usb 1-1: Product: FT260
[ 6892.199803] usb 1-1: Manufacturer: FTDI
[ 6902.262497] hid-generic 0003:0403:6030.0005: usb_submit_urb(ctrl) failed: -1
[ 6902.276074] hid-generic 0003:0403:6030.0005: timeout initializing reports
[ 6902.284785] hid-generic 0003:0403:6030.0005: hidraw0: USB HID v1.11 Device [FTDI FT260] on usb-musb-hdrc.1.auto-1/input0
[ 6912.310492] hid-generic 0003:0403:6030.0006: usb_submit_urb(ctrl) failed: -1
[ 6912.322341] hid-generic 0003:0403:6030.0006: timeout initializing reports
[ 6912.330402] hid-generic 0003:0403:6030.0006: hidraw1: USB HID v1.11 Device [FTDI FT260] on usb-musb-hdrc.1.auto-1/input1
}}}
HIDデバイスが2つ認識されているが、[http://www.ftdichip.com/Support/Documents/ProgramGuides/AN_394_User_Guide_for_FT260.pdf FT260のアプリケーションノート]の6ページ目{{{1.2 FT260 HID Interfaces and Endpoints}}}をみると、I2CとUARTがそれぞれ別のHIDとして認識されるようだ。[[br]]
{{{hidraw0}}}がI2C、{{{hidraw1}}}がUARTらしい。
ジャンパを設定し直して、I2Cのみにしてみると、
{{{
[ 7774.216655] musb-hdrc: 28/31 max ep, 16384/16384 memory
[ 7774.555547] musb-hdrc musb-hdrc.1.auto: Babble
[ 7774.560500] musb-hdrc musb-hdrc.1.auto: Babble
[ 7774.565029] musb-hdrc: setup fifo_mode 4
[ 7774.565083] musb-hdrc: 28/31 max ep, 16384/16384 memory
[ 7774.962288] usb 1-1: new full-speed USB device number 31 using musb-hdrc
[ 7775.093398] usb 1-1: New USB device found, idVendor=0403, idProduct=6030
[ 7775.093436] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 7775.093452] usb 1-1: Product: FT260
[ 7775.093467] usb 1-1: Manufacturer: FTDI
[ 7785.130492] hid-generic 0003:0403:6030.0007: usb_submit_urb(ctrl) failed: -1
[ 7785.144095] hid-generic 0003:0403:6030.0007: timeout initializing reports
[ 7785.153751] hid-generic 0003:0403:6030.0007: hidraw0: USB HID v1.11 Device [FTDI FT260] on usb-musb-hdrc.1.auto-1/input0
}}}
となり、I2Cのみになるので、これで実験することにする。
== pythonでのテスト ==
以前、帯名さんがHIDデバイスのテストをした時のドキュメントを参考にしてみる。[[br]]
まずは、FT260の設定を取得してみる。
{{{
oot@beaglebone:~# ipython --nosep
Python 2.7.9 (default, Aug 13 2016, 17:56:53)
Type "copyright", "credits" or "license" for more information.
IPython 2.3.0 -- An enhanced Interactive Python.
? -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help -> Python's own help system.
object? -> Details about 'object', use 'object??' for extra details.
In [1]: import hid
In [2]: h = hid.device()
In [3]:
In [3]: h.open(0x0403, 0x6030)
In [4]: h.set_nonblocking(1)
Out[4]: 0
In [5]: h.write([0xA1] + [0]*63)
Out[5]: 64
In [6]: h.read(64)
Out[6]: []
In [7]: h.write([0xA0] + [0]*63)
Out[7]: 64
In [8]: h.write([0xA0] + [0]*63)
Out[8]: 64
In [9]: h.read(64)
Out[9]: []
In [10]: h.write([0xA0] + [0]*11)
Out[10]: -1
In [11]: h.write([0xA0] + [0]*63)
Out[11]: -1
In [12]: h.read(64)
Out[12]: []
In [13]: h.read(64)
Out[13]: []
In [14]: h.write([0xA0] + [0]*63)
Out[14]: -1
In [15]: h.write([0xC0] + [0]*63)
Out[15]: -1
In [16]: h.close()
}}}
うまくいかない、、、[[br]]
色々見ていったところ、取得するデータによって使用する関数が違うらしい。[[br]]
[http://www.ftdichip.com/Support/Documents/ProgramGuides/AN_394_User_Guide_for_FT260.pdf FT260のアプリケーションノート]を見てみると、15ページの{{{4.3 FT260 Report ID List}}}には、
|| Report ID || Type || description ||
|| 0xA0 || Feature || Chip code ||
|| 0xA1 || Feature || System Setting ||
|| 0xB0 || Feature || GPIO ||
|| 0xB1 || Input || Interrupt Status (from UART interface) ||
|| 0xC0 || Feature || I2C Status ||
|| 0xC2 || Output || I2C Read Request ||
|| 0xD0 ~ 0xDE || Input, Output|| I2C Report ||
|| 0xE0 || Feature || UART Status ||
|| 0xE2 || Feature || UART RI and DCD Status ||
|| 0xF0 ~0xFE ||Input, Output || UART Report ||
と書いてあり、{{{Feature}}}へのアクセスは{{{send_feature_report, get_feature_report}}}で行い、{{{INPUT,OUTPUT}}}は{{{write,read}}}で行うらしい。
{{{
root@beaglebone:~# ipython --nosep
Python 2.7.9 (default, Aug 13 2016, 17:56:53)
Type "copyright", "credits" or "license" for more information.
IPython 2.3.0 -- An enhanced Interactive Python.
? -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help -> Python's own help system.
object? -> Details about 'object', use 'object??' for extra details.
In [1]: import hid
In [2]: h = hid.device()
In [3]: h.open(0x0403, 0x6030)
In [4]: h.get_feature_report(0xA0,13)
Out[4]: [160, 2, 96, 2, 0, 2, 96, 1, 0, 1, 1, 3, 0]
In [5]: h.get_feature_report(0xA1,64)
Out[5]:
[161,
1,
2,
0,
1,
1,
0,
0,
1,
3,
6,
1,
1,
12,
1,
0,
0,
0,
0,
0,
172,
0,
188,
0,
204]
}}}
これでやっと、FT260にアクセスできることが確認できた。[[br]]
次にI2C上のスレーブデバイスにアクセス。[[br]]
接続しているI2Cデバイスは、[http://www.analog.com/jp/products/analog-to-digital-converters/integrated-special-purpose-converters/digital-temperature-sensors/adt7410.html#product-overview ADT7410]を基板化した、[http://akizukidenshi.com/catalog/g/gM-06675/ 秋月電子 ADT7410使用 高精度・高分解能 I2C・16Bit 温度センサモジュール]。
FT260開発ボードの3.3V電源と、IO0,IO1を接続した。
{{{
FT260 <-> ADT7410
3.3V (JP6-1) VDD
IO0 (JP6-11) SCL
IO1 (JP6-10) SDA
GND GND
}}}
温度センサのI2Cアドレスは0x48としておく。
{{{
In [1]: import hid
In [2]: h = hid.device()
In [3]: h.open(0x0403, 0x6030)
In [4]: h.set_nonblocking(1)
Out[4]: 0
In [5]: h.write([0xC2, 0x48, 0x06, 0x04, 0x00])
Out[5]: 5
In [6]: h.read(64)
Out[6]: [208, 4, 13, 88, 128, 0, 50, 0]
}}}
先頭2byteはFT260のHIDコマンドヘッダなので、{{{[13, 88, 128, 0, 50, 0]}}}がデータ。[[br]]
取得するデータ数は4byteだけど、6byteデータが出てくる。多分、HIDヘッダも含めて4byte単位でデータが出てくるのだろう。[[br]]
ADT7410のデータシートから、{{{[13, 88]}}}が温度データ、次の{{{[128]}}}がStatus、{{{[0]}}}がConfigurationとなっているようだ。[[br]]
Configurationの7bit目が1なら16bit,0なら13bitデータになる。[[br]]
13bit時のデータは[15:3]が有効なデータなので、計算式は、
{{{
ADC_DEC = ((HSB<<8) | LSB)>>3
Positive_Temp[℃] = ADC_DEC /16.0
Negative_Temp[℃] = (ADC_DEC - 8192)/16
}}}
となるらしい。[[br]]
この計算式に当てはめると、{{{LSB=88,HSB=13}}}の場合、{{{26.6875[℃]}}}となる。[[br]]
今回は16bitで使いたいので、Configration Registorの7bit目を1にする必要がある。[[br]]
どうやるか試行錯誤して、I2Cプロトコルにおける設定(書き込み)は、書き込みアドレスの後ろに設定データを付けて送ればいいことを思い出した。[[br]]
{{{
In [25]: h.write([0xD0, 0x48, 0x06, 0x02, 0x03,0x80,0x00,0x00])
Out[25]: 8
In [26]: h.write([0xC2, 0x48, 0x06, 0x04, 0x00])
Out[26]: 5
In [27]: h.read(64)
Out[27]: [208, 4, 13, 75, 128, 128, 0, 0]
}}}
レジスタ設定後にデータを読み出すと、Configuration Registorが128(0x80)となっていて、16bitになっていることがわかる。[[br]]
16bit時の計算式は、
{{{
ADC_DEC = ((HSB<<8) | LSB)
Positive_Temp[℃] = ADC_DEC /128
Negative_Temp[℃] = (ADC_DEC - 65536)/128
}}}
となる。計算式に当てはめると、{{{LSB=75,HSB=13}}}の場合、{{{26.5859375[℃]}}}となる。[[br]]
これで、FT260をつかったI2Cデバイスの制御に必要なコマンドは全て使い方が分かったので、IOC化することができそうである。
== 複数デバイス接続 ==
FT260開発ボードをUSBハブで複数繋いでみると、当たり前だが普通に認識して/dev/hidraw*が生成される。[[br]]
FT232RやFT230Xには、デフォルトでチップ毎にシリアル番号が設定されていて、それを使えばデバイス名の固定が可能だが、FT260にはそれがない。
{{{
debian@beaglebone:~$ udevadm info -a -p $(udevadm info -q path -n /dev/hidraw0)
:
looking at parent device '/devices/platform/ocp/47400000.usb/47401c00.usb/musb-hdrc.1.auto/usb1/1-1/1-1.2':
KERNELS=="1-1.2"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{authorized}=="1"
ATTRS{avoid_reset_quirk}=="0"
ATTRS{bConfigurationValue}=="1"
ATTRS{bDeviceClass}=="00"
ATTRS{bDeviceProtocol}=="00"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bMaxPacketSize0}=="64"
ATTRS{bMaxPower}=="100mA"
ATTRS{bNumConfigurations}=="1"
ATTRS{bNumInterfaces}==" 1"
ATTRS{bcdDevice}=="2200"
ATTRS{bmAttributes}=="a0"
ATTRS{busnum}=="1"
ATTRS{configuration}==""
ATTRS{devnum}=="118"
ATTRS{devpath}=="1.2"
ATTRS{idProduct}=="6030"
ATTRS{idVendor}=="0403"
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="FTDI"
ATTRS{maxchild}=="0"
ATTRS{product}=="FT260"
ATTRS{quirks}=="0x0"
ATTRS{removable}=="unknown"
ATTRS{speed}=="12"
ATTRS{urbnum}=="16"
ATTRS{version}==" 2.00"
:
}}}
FT232R等の他のチップでは、{{{ATTRS{SerialNumber}==xxxx}}}のような表示が入る。(詳細な属性名は不明)[[br]]
データシートを見ると、I2C接続した外付けEEPROMに書き込むと、表示されるようになるらしい。[[br]]
実験の初めにEEPROMを外して実験していたが、接続しなおしてから、シリアル番号を書き込んでみる。[[br]]
設定の変更はI2Cコマンドでできるようだが、[http://www.ftdichip.com/Support/Utilities.htm#FT_Prog FT_Prog]というユティリティをFTDIが用意してくれているので、今回はそれを使う。[[br]]
[[Image(FT_Prog.png,300)]]
{{{USB String Descriptors -> Serial Number Enabled}}}と{{{Auto Generate Serial No}}}にチェックを入れて、メインメニューの{{{DEVICES -> Program}}}を選択すると、
[[Image(FT_Prog_Write.png)]]
のダイアログボックスで{{{Program}}}すれば自動的にシリアル番号が生成されてEEPROMに書き込まれる。[[br]]
'''ここでの注意点は、プログラムのデフォルトのままだとvendoridが0403から0803に書き変わってしまうので、書き込み前にチェックすること。'''[[br]]
書き込み実行後に再度デバイス情報を見てみると、今度は{{{ATTRS{serial}==}}}が設定されている。[[br]]
{{{
debian@beaglebone:~$ udevadm info -a -p $(udevadm info -q path -n /dev/hidraw0)
:
looking at parent device '/devices/platform/ocp/47400000.usb/47401c00.usb/musb-hdrc.1.auto/usb1/1-1/1-1.1':
KERNELS=="1-1.1"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{authorized}=="1"
ATTRS{avoid_reset_quirk}=="0"
ATTRS{bConfigurationValue}=="1"
ATTRS{bDeviceClass}=="00"
ATTRS{bDeviceProtocol}=="00"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bMaxPacketSize0}=="64"
ATTRS{bMaxPower}=="128mA"
ATTRS{bNumConfigurations}=="1"
ATTRS{bNumInterfaces}==" 1"
ATTRS{bcdDevice}=="2200"
ATTRS{bmAttributes}=="a0"
ATTRS{busnum}=="1"
ATTRS{configuration}==""
ATTRS{devnum}=="124"
ATTRS{devpath}=="1.1"
ATTRS{idProduct}=="6030"
ATTRS{idVendor}=="0403"
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="FTDI"
ATTRS{maxchild}=="0"
ATTRS{product}=="FT260"
ATTRS{quirks}=="0x0"
ATTRS{removable}=="unknown"
ATTRS{serial}=="FT27KQDF"
ATTRS{speed}=="12"
ATTRS{urbnum}=="18"
ATTRS{version}==" 2.00"
:
}}}
デバイスを再接続後、pythonでアクセスしてみる。
{{{
root@beaglebone:~# ipython --nosep
In [1]: import hid
In [2]: h = hid.device()
In [3]: h.open(0x0403, 0x6030, "FT27KQDF")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in ()
----> 1 h.open(0x0403, 0x6030, "FT27KQDF")
TypeError: Argument 'serial_number' has incorrect type (expected unicode, got str)
In [4]: h.open(0x0403, 0x6030, u"FT27KQDF")
}}}
シリアル番号をunicodeで設定したら、オープンできたので、大丈夫そう。[[br]]
== IOC ==
接続方法と使用するコマンドがわかったので、IOCを作成する。[[br]]
作成するレコードは、ADT7410のADCを16bitに設定するコマンドと、温度を取得するコマンドのみ。[[br]]
以前作った、[wiki:/epics/bbb/debian/USBHID/USB_SP4T drvAsynUSBHID]を使って、コマンドを実装。[[br]]
ここで気付いたのだが、drvAsynUSBHIDはInput,Outputを使った制御には使えるが、USBHIDデバイス自体を制御するFeatureコマンドには対応していない。[[br]]
今回もFeatureコマンドを使わなくても目的は達成できたが、今後Featureコマンドが必須の制御をする場合には、何らかの解決策を考える必要がありそう。[[br]]
=== 使用コマンド(FT260) ===
今回使用するFT260上のコマンドは、
* I2C Write Request(0xD0~0xDE)
* I2C Read Request(0xC2)
* I2C Input Report(0xD0~0xDE)
のみである。I2C Input ReportはReadで返ってくるフォーマットの定義なので、I2C Write RequestとI2C Read Requestだけを実装する。
=== protocol ファイル ===
今回作成したプロトコルファイルがこれ。
{{{
TERMINATOR="";
LockTimeout = 500;
ReplyTimeout = 100;
ReadTimeout = 100;
WriteTimeout = 100;
MaxInput=8;
wr_4 = 0xD0;
rd = 0xC2;
flg = 0x06;
len = 0x04;
#####################
# $1:I2C slave device addr
#####################
# Set ADT7410 ADC Resolution(16bit)
setADCRes {
out $wr_4 $1 $flg 0x02 0x03 0x80 0x00 0x00;
}
# Read ADT7410 templature value
getTemp {
out $rd $1 $flg $len 0x00;
in $wr_4 $len "%(\$2)01r" "%01r" "%*4r";
}
}}}
I2Cデバイスのアドレスをdbファイルから設定できるようにして、温度レジスタを別々に読み込んでCALCで計算するようにした。[[br]]
=== DBファイル ===
{{{
record(bo, "$(user):ADT7410:init")
{
field(DESC, "Set ADT7410 ADC 16bit")
field(DTYP, "stream")
field(OUT, "@FT260_ADT7410.proto setADCRes($(i2caddr)) $(dev)")
}
record(longin, "$(user):temp:reg:HSB")
{
field(DESC, "Get temperature registor HSB")
}
record(longin, "$(user):temp:reg:LSB")
{
field(DESC, "Get temperature registor LSB")
field(DTYP, "stream")
field(INP, "@FT260_ADT7410.proto getTemp($(i2caddr),$(user):temp:reg:HSB) $(dev)")
field(SCAN, "$(scan)")
field(FLNK, "$(user):temp")
}
record(calc, "$(user):temp")
{
field(DESC, "Get temperature")
field(INPA, "$(user):temp:reg:LSB")
field(INPB, "$(user):temp:reg:HSB")
field(CALC, "(B&0x80)=0x80?(((B<<8)|A)-65536)/128:((B<<8)|A)/128")
field(PREC, "5")
field(EGU, "degC")
}
}}}