BLOG.tass.io

超小型Arduino互換ボード Adafruit Trinket でシリアル通信を実現する

2014-12-20

先日の記事にも書いた超小型Arduino互換ボードの「Adafruit Trinket」ですが、最大の難点はシリアルポートを持っていないことでした。 せっかくセンサー類をつけても、ホストPCからは読み取ることもできず、フィードバックもArduino内に閉じて行うしかありません。

ただ、Adafruit社もそこはどうにかしたいと思ったのか、ソフトウェアで仮想ポートを作って無理やりUSBで通信しちゃおうという記事がありました! Windows用だと専用のexeもあったりして簡単そうなのですが、ここは敢えてOSXでやってみました(•̀ᴗ•́)و ̑̑

環境

  • OSX Yosemite 10.10.1
  • Python 2.7 (Python 3では動かないらしいので注意)
  • Adafruit Trinket 5Vモデル(16MHzで実行する必要があるため、3Vモデルでは不可)

(mini-USBが大きく見えますね//)

仕組み:シリアルポートがないなら仮想ポートを作ればいいじゃない

前述の通り、Trinketにはデバッグ用のシリアルポートがないため、ホストPCとArduinoの間でデータのやり取りを行うことが出来ません。 ただ、USB自体は機能しているので、USBのControl Transferを使って仮想ポートに繋いじゃおうという仕掛けです。

「TrinketFakeUsbSerialHostSW」 というミドルウェアで、Pythonのlibusbバインディングを使ってTrinketとの通信を行います。 TrinketからホストPCへデータを送るためには、interrupt-in endpointを使います。 PCは常時ポーリングしてendpointが来ていないかをチェックしており、もしYESであれば受信を開始します。 通信は仮想ポートに繋いであるので、ホストPCは通常のシリアルポートとして扱うことができます。

Trinket側にもユーザーライブラリ 「TrinketFakeUsbSerial」 を組み込むことで、これらの通信を実現します。 Serialオブジェクトではなく、独自の TFUSerialオブジェクトを使ってホストPCとの通信を行います。 ちょっとした決まり事(setupTFUSerial.begin()を呼ぶ、loopTFUSerial.task()を呼ぶ等)を守るだけで、殆どスケッチを変更することなく対応が可能です。

Arduino側のセットアップ

ここでは、Trinket用のArduino IDEを使ってます。

まず、ユーザーライブラリを組み込みます。本家のリポジトリからTrinketFakeUsbSerial_allfiles_20140131a.zipをダウンロードしてきます。

解凍すると、Arduino_Libraryの中に「TrinketFakeUsbSerial」というライブラリのディレクトリがありますので、Arduino IDEの「スケッチ」メニューから「ライブラリを使用」「Add Library」から読み込みます。

「TrinketFakeUsbSerial」の中の「examples」ディレクトリの中にサンプルコードが入っているので、Arduino IDEで開いて検証・コンパイルの上でマイコンボードに書き込んでおきましょう。

ホストPC(OSX)側のセットアップ

OSX/Linuxでの実現方法はこちらの記事からになります。

仮想ポートの作成

Windows向けには仮想ポート作成ユーティリティのcom0comがありますが、OSX/Linuxではsocatコマンドを使います。socatはbrewからインストールできます。

$ brew install socat

socatが使えるようになれば、あとはCLIから作成するポートを指定して起動します。

$ socat -d -d -d PTY,link=COM8 PTY,link=COM9

これで/dev/ttys***が2つ作成され、カレントディレクトリにシンボリックリンクとしてCOM8COM9というファイルが作成されます。

ミドルウェアの起動

Windows向けには通信用のミドルウェアTrinketFakeUsbSerialHostSW.exeがありますが、OSX/Linux向けにはPythonスクリプトが用意されています。 まずはじめに、必要なライブラリをインストールしておきます。

  • PyUSB
  • pySerial

一応、いま入っているPythonのバージョンを確認しておきます。

$ python --version
Python 2.7.6

PyUSBpySerialのライブラリ2つは、easy_installを使って、CLIから簡単にインストールできます。

$ sudo easy_install PyUSB
$ sudo easy_install pySerial

本当にちゃんとインストールされたのか確認してみます。easy-install.pthというファイルにインストール済みのパッケージとバージョンが記載されています。 easy_installはPythonのバージョン毎にインストールされるので、/Library/Python/{python version}/site-packages/easy-install.pthの中身を覗いてみます。

$ cat /Library/Python/2.7/site-packages/easy-install.pth
import sys; sys.__plen = len(sys.path)
./pyusb-1.0.0b2-py2.7.egg
./pyserial-2.7-py2.7.egg
import sys; new=sys.path[sys.__plen:]; del sys.path[sys.__plen:]; p=getattr(sys,'__egginsert',0); sys.path[p:p]=new; sys.__egginsert = p+len(new)

ちゃんと入っているようですε-(´∀`*)ホッ

通信用ミドルウェア(TrinketFakeUsbSerialHostSW.py)は、本家もあるのですが、私の環境ではうまく動きませんでした。 PyUSBのバージョンが違うためか、タイムアウト時のエラーメッセージに食い違いがあるようでした。 一応、修正版のコードがこちらにあるので、大丈夫そうなら使って下さい。
※ 本家にもPull Requestを投げておきました

先ほど作成した仮想ポート(COM8)を指定して、このスクリプトを実行します。

$ python ./TrinketFakeUsbSerialHostSW.py -v -p COM8
TrinketFakeUsbSerialHostSW Python Edition
port is  COM8
Traceback (most recent call last):
File "./TrinketFakeUsbSerialHostSW.py", line 160, in <module>
main(sys.argv[1:])
File "./TrinketFakeUsbSerialHostSW.py", line 68, in main
trinketHandle = findTrinket()
File "./TrinketFakeUsbSerialHostSW.py", line 133, in findTrinket
device = usb.core.find(idVendor = 0x1781, idProduct = 0x1111)
File "build/bdist.macosx-10.9-intel/egg/usb/core.py", line 1199, in find
ValueError: No backend available

うがが、エラーになりました…。「ValueError: No backend available」と出力されているので、どうも私の環境だとlibusbが参照できていないようです。 無理矢理ですが、brewでインストールしたlibusbのlibディレクトリへDYLD_LIBRARY_PATHで直接パスを指定してみるとスクリプトが動きました。 力技ですが、きっと普通の人の環境なら大丈夫なはず。(boxenでbrewを入れた方法が悪そう。むむぅ。)

$ export DYLD_LIBRARY_PATH=/opt/boxen/homebrew/Cellar/libusb/1.0.19/lib
$ python ./TrinketFakeUsbSerialHostSW.py -v -p COM8
TrinketFakeUsbSerialHostSW Python Edition
port is  COM8
Trinket Connected

これで接続は完了です。COM8とCOM9はつながっているので、ホストPC側はCOM9に対してデータを送受信すればOKです。 試しに、COM9に'a'と'b'と送ってみます。

$ echo a > ./COM9
$ echo b > ./COM9

すると、COM9 -> COM8 -> USB -> Trinketと経由してデータが届きます。 先ほどのサンプルのスケッチが書き込まれていれば、Trinketから返信が届いているはずです。

$ cat ./COM9
hello world
8167871

こんな感じで、無事に仮想ポートを使ってTrinketと通信を行うことができました(∩´∀`)∩ワーイ


Michael Kuroneko

Written by Michael Kuroneko. Follow me on twitter, github.