例題21-2:シリアルポートとパラレルポートを使…えるはず

B: えーと。Aさん。そこへ座りなさい。

A: はぁい。

B: …わかっていますね?

A: 悪乗りが過ぎました。反省しております。反省するのは私じゃなくて作者のような気がするんだけど…

B: そこ! 何か言いましたかぁ? 苦情のメールがこんなに来ているんですよ?

A: こんなにってたったの2通…

B: 甘ぁい! 普段新作を公開しても1通もメールなんて来ないのに2通も苦情メールが来るなんて非常事態!! 深く反省して真面目に生まれ変わった姿を皆様に示さなければならぬのです!

A: とほほ… 前回のB君の偽者に続いて今回もいつものB君と違ってやりにくい… まさか今回も偽者じゃないよね?

B: Aさ~~~~~ん。まだそんなことを言いますか。それっ、無駄口叩いていないでさっさと本題を始める!

A: ううっ、わかった、わかりましたよ。今回はpythonからシリアルポートとパラレルポートを使う方法の説明です。

B: ふむ。チュートリアルで質問があったテーマですな。結構結構。

A: ですが、実は私自身はもうシリアルポートやパラレルポートを使った実験をしないので、って言うかシリアルポートやパラレルポートが付いている機器を持っていないので、動作確認はしとらんのですよ。ですから公式ドキュメントの紹介とかでご勘弁いただきたいのですが…。

B: ま、持ってないものは仕方ありませんな。続けたまい。

A: (このB君もやりにくいな…) さて、まずシリアルポートですが、VisionEggとPsychoPyのどちらの場合でもpySerial( http://pyserial.sourceforge.net/ )を使用します。PsychoPyのStandAlone版にはpySerialがインストールされているので、そのまま使用することが出来ます。PsychoPyを手動でインストールしている場合や、VisionEggを使っている場合はpySerialのプロジェクトページへ行ってinstallationの項目を参考にしてください。 pipeasy_install が使える場合は以下のようにコマンドシェルからインストールすることが出来ます。Pythonのインストール場所によっては管理者の権限が必要なのでお忘れなく。

pip install pyserial
easy_install -U pyserial

B: pipもeasy_installも使えない場合は?

A: まあVisionEggにせよPsychoPyにせよ、使える状態になっているのであればフツーsetuptools(distribute)がインストールしてあるはずなので、easy_installは使えるはずです。Ubuntuな人なんかはpython-serialというパッケージ名でインストールできるので、apt-getでちょちょいのちょいですね。

sudo apt-get install python-serial

A: Windowsな人はpypiにWindows用インストーラーがあるのでそちらをご利用いただくとよいかと( https://pypi.python.org/pypi/pyserial )。PsychoPyもVisionEggも今のところ(2013年11月現在)Python2.x系で動きますので、Python 2.x系用のインストーラーを使ってくださいね。Python 3.x系用のインストーラーには_py3kとついています。

../_images/21-2-01.png

A: さて使い方ですが、あまりいう事はありません。ポート0を開いて'6F,01\r'という文字列を書きこむには以下のようにします。

 1#coding: utf-8
 2#pySerialをインストールするとserialというモジュールが使えるようになる
 3import serial
 4
 5#COM1を開く。ポートは0から数えるので通常COM1は0番目のポートである
 6com01 = serial.Serial(0)
 7
 8#COM1に書きこむ
 9com01.write('6F,01\r')
10
11#終わったらclose
12#続けて書きこむ場合はいちいちcloseしなくてもいい
13com01.close()

B: あら、コメント行を除いたら4行しかありませんね。これは簡単。

A: 読み込む場合は以下の通り。

2014/07/26訂正:writeとreadが逆になっていました!大ボケです。申し訳ございません。

 1#coding: utf-8
 2#pySerialをインストールするとserialというモジュールが使えるようになる
 3import serial
 4
 5#COM1を開く。ポートは0から数えるので通常COM1は0番目のポートである
 6com01 = serial.Serial(0)
 7
 8#COM1から変数resに6byte読み込む
 9res = com01.read(6)
10
11#終わったらclose
12#続けて読み込む場合はいちいちcloseしなくてもいい
13com01.close()

B: writeがreadになっただけですね。読んだり書いたり交互に出来ます?

A: もちろん。複数のポートを開くことも出来る。

B: もしかしてこれでおしまい?

A: (いつもの調子になってきたかな?) おしまい、言いたいところだが恐らくシリアルポートを使う方はボーレートやタイムアウトの設定について知りたいはず。serial.Serial()を使ってポートを開く際に、以下のようなオプションを指定することが出来る。

パラメータ

内容

初期値

port

ポート番号またはデバイス名を指定する

None

baudrate

Baud ボーレートを数値で指定する(9600とか115200とか)

9600

bytesize

データのビット数を指定する。FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITSのいずれか。

EIGHTBITS

parity

パリティを指定する。PARITY_NONE, PARITY_EVEN, PARITY_ODD PARITY_MARK, PARITY_SPACEのいずれか。

PARITY_NONE

stopbits

ストップビットを指定する。STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWOのいずれか。

STOPBITS_ONE

timeout

読み取り時のタイムアウトを指定する。単位は秒。Noneなら無限に待つ。0ならば待たずに終了する。

None

xonxoff

XON/XOFFの使用を指定する。TrueかFalseのいずれか。

False

rtscts

RTS/CTSの使用を指定する。TrueかFalseのいずれか。

False

dsrdtr

DSR/DTRの使用を指定する。TrueかFalseのいずれか。

False

writeTimeout

書き込み時のタイムアウトを指定する。単位は秒。Noneなら無限に待つ。0ならば待たずに終了する。

None

interCharTimeout

Inter-character timeoutを指定する。Noneならば使用しない。

None

B: むむむ。これはシリアルポートの設定項目のことを知らないと何のことだかさっぱりわからんというパターンですか。

A: だね。シリアルポートを使いたい人は恐らく具体的に使いたい機器があってのことだろうから、その機器のシリアルポートの設定をよーっく見て一致するようにしてほしい。ボーレート115200、パリティPARITY_EVEN、タイムアウト1秒を指定してポートを開くには以下のように書けばよい。

com = serial.Serial(0,
                    baudrate=115200,
                    parity = serial.PARITY_EVEN,
                    timeout=1)

B: いちいちserial.PARITY_EVENって書かないといけないんですね。

A: pygameのキー名と一緒だね。from serial import *としてインポートしていれば直にPARITY_EVENと書けるけど、本講座ではモジュール名を明記する方法を推奨している。

B: pygameのキー名はよくimport *してますよね、Aさん。

A: ほっとけ。さて、心理実験で外部機器をシリアルポートで連動させるような用途だと、大体の場合は上記のパラメータの設定とread()、write()メソッドがあればそれで済むと思う。他にも受信バッファの文字数を返すinWaiting()とか、送信バッファ内のデータを全て送信するまで待つflush()とか、フローを制御するsetRTS()とかsetDTR()とか、いろんなメソッドがあるので詳しくはhelp(serial.Serial)してみて欲しい。 http://pyserial.sourceforge.net/pyserial_api.html#classes なんかでもクラスメソッドは確認できる。英語だけど。

B: ぐはあ。

A: 続いてパラレルポート。シリアルポートは対応する機器を「今」持っていないというだけでいろいろ使用経験があるからまあ紹介する資格あるか…と思ってたんだけど、パラレルポートは本当に使ったことがない。パラレルポートっちゅーとUSBが普及する前のプリンターとかを思い出すなあ、といったレベル。

B: どういうレベルなのかいまいちよくわかりませんが…。

A: まあ年寄りの寝言だから気にするな。pySerialのwebページを確認していただいた方はすでにお気づきかも知れないが、pySerialと同じサイトでpyParallelというモジュールが公開されている( http://pyserial.sourceforge.net/pyparallel.html )。ただ、これはまだ開発中という位置漬けで、しかも2005年1月27日公開のpyparallel-0.2から更新されていない。

B: Aさん。位置づけ。漬けてどうするんですか。

A: おっと失礼。pyparallelのページに簡単な使用例が出ているのでそちらを参考にしてほしい。繰り返す通り動作確認できるハードウェアが手元にないし、なにしろ開発版という位置づけなので、実際どの程度使えるのかわからない。

B: うーん。

A: そんなわけでVisionEggユーザーはちょっと困ってしまうわけだが、PsychoPyにはpsychopy.parallelというモジュールが用意されていて、パラレルポートが利用できる。パラレルポート必須の人はPsychoPyを選んだ方が良さそうだな。Coderのデモコードの中にparallelPortOutput.pyというサンプルコードがある。短いのでまるごと引用してみよう。

../_images/21-2-02.png
 1#!/usr/bin/env python
 2#this is win32 only - I have no idea how to use parallel ports on a Mac! jwp
 3from psychopy import visual, core, logging
 4from psychopy import parallel
 5
 6nFramesOn = 5
 7nFramesOff = 30
 8nCycles = 2
 9parallel.setPortAddress(0x378)#address for parallel port on many machines
10pinNumber = 2#choose a pin to write to (2-9).
11
12#setup the stimuli and other objects we need
13myWin = visual.Window([1280, 1024],allowGUI=False)#make a window
14myWin.flip()#present it
15myStim = visual.PatchStim(myWin, tex=None, mask=None, color='white', size=2)
16myClock = core.Clock() #just to keep track of time
17
18#present a stimulus for EXACTLY 20 frames and exactly 5 cycles
19for cycleN in range(nCycles):
20    for frameN in range(nFramesOff):
21        #don't draw, just refresh the window
22        myWin.flip()
23        parallel.setData(0)#sets all pins low
24
25    for frameN in range(nFramesOn):
26        myStim.draw()
27        myWin.flip()
28        #immediately *after* screen refresh set pins as desired
29        parallel.setPin(2,1)#sets just this pin to be high
30
31#report the mean time afterwards
32print 'total time=', myClock.getTime()
33print 'avg frame rate=', myWin.fps()
34#set pins back to low
35myWin.flip()
36parallel.setData(0)#sets all pins low again

B: ふむむん…って、2行目! I have no idea how to use parallel ports on a Macって!

A: ああ。読んで字のごとくだな。やはりこういうところはWindowsやLinuxの方が強い。ごにょごにょと書いてあるけど大事な行は数行だけ。まず4行目。psychopy.parallelをインポート。そして9行目、setPortAddress()でポートを読み書きする準備。

B: 0x378てなんですか?

A: ああ。0x378なんて数字を見るとな。若かりしころを思い出すのだ。あの蒸し暑い研究室。オンボードチップの技術資料をわかるまで何度も読み返して、ああ疲れたと思って冷蔵庫を開けたらスポーツドリンクのペットボトルの中に綿のようなカビが…。ああああ!!

B: もしもし?もしもーし?

A: あ、ああ、なんだか忌まわしいものが頭をよぎって取り乱してしまった。とにかく0x378というと昔のコンピュータでパラレルポートを使っていた人はピーンとくる懐かしい数字なのだ。他には0x278、0x3BCといった辺りにパラレルポートが設定されていることが多い。

B: ???

A: もう21世紀になって10年以上も経って未だこんな数字を見るとはな。これはI/Oレジスタアドレス、と言ったっけな。PCに搭載されている入出力機器にアクセスする時に、この番地(アドレス)を使ってアクセスするのだ。0x378番地には通常パラレルポートさんが住んでいて、0x378番地に行ったらパラレルポートさんと会えるのだ。

B: 番地、ですか。

A: 住所でも1丁目2番地、3番地、4番地…という具合に番号が付いていて、番号を指定すりゃ郵便物が届くだろ。同じことよ。数多くのメーカーがWindowsやその前身のMS-DOSが動くパーソナルコンピューターを販売してきたが、そんな芸当ができたのはメーカーが違ってもどの機器をどの番地に置くかを共通化していたからだ。だから、今使っているPCにパラレルポートが存在するならば、0x378番地や0x278番地にアクセスすると使用できるのである。

B: な、なるほど。なんとなく雰囲気はわかりました。でも、「存在するならば」って、存在しないこともあるんですか?

A: 君のノートPCにはパラレルポートが付いているのかね。それはともかく、このサンプルプログラムを実行してみて、0x378でパラレルポートが開けなかったら、以下のようにポートに対してデータを出力しようとした時にNoneTypeオブジェクトはsetDataなんて属性を持ってないよとエラーが出て終了する。0x3BCや0x278を試してもダメなら、一般的なアドレスにパラレルポートが存在しない。増設式のパラレルポートとかを積んでる場合は、どの番地に配置されているのを調べるとか、0x378などに配置されれるように設定を変更する必要がる。

../_images/21-2-03.png

B: どうやって番地を調べたり設定を変更したりするんですか?

A: ボードのドライバに依るんじゃないかな。知らない。

B: そんなぁ。

A: ブツがあれば頑張って試すけど、さすがにそのためだけに購入するほど資金に余裕がなくてな。さて、9行目のsetPortAddress()でアドレスを指定して使用準備はOK。あとはデータを出力する時にsetData()、またはsetPin()を使う。それぞれ23行目、29行目だね。setDataは8bitの整数で8本のピンの状態を一度に設定する時に、setPinはいずれか1本のピンの値だけを設定する時に使う。実にややこしいことにピン番号は0から7でもなく、1から8でもなく 2から9 なので注意してほしい。

B: なんでまたそんな中途半端なことを。

A: んー。私の記憶に間違いがなければ、25ピンのパラレルポートの2番から9番のピンが入出力用のピンだったはず。だからここは文字通りピンの番号を指定していると考えればいいだろうな。

B: むむ。なるほど。

A: で、サンプルプログラムにはないけれども、ピンからデータを読むにはreadPin()というメソッドを使う。help(psychopy.parallel.readPin)してみると、ピン番号は2-13、15を指定できると書いてある。10番以降のピンは…なんだったかいな。忘れた。ずいぶん昔のことだし、私自身は買ってきたプリンターをただ単につないだりしただけで、自分でパラレルポートを使う実験を書いたことはないからなあ。

B: ふうん。それにしてもなんで読み込みはピン1本ずつしか出来ないんですかね。

A: ま、作るのが面倒くさかったが使い道がないか、だろうけど。とにかくこれでシリアルポートとパラレルポートの解説はおしまい。

B: ふっ、ふふふふ…。

A: な、なんだ突然。

B: はははっ、どうですAさん! やっぱりぼくと一緒の方が断然やりやすいでしょう! 思い知りましたかぼくのありがたさを!

A: はぁ? 何が?

B: またまたすっとぼけてー。いつもと調子が違ってやりにくいとか思ってたでしょ。顔に書いてあります。

A: うむ。確かに調子が違って落ち着かなかったな。前回の謎の偽者も含めて。

B: ほらほらー。

A: B君がいつもと違って落ち着かないというのと、B君が一緒の方がやりやすいというのは全然別の話である。最初からB君がいない方がやりやすいという可能性もあるではないか。

B: なっ、そんなことを言いやがりますかこの人は。僕を(自主規制)な目に遭わせただけでは飽き足らず、やっとの思いで帰ってきた僕に対してこの仕打ち。くそっ、こうなったらとことん居座って妨害してやる!

A: やれやれ。ま、せいぜい妨害してくれたまえ。ところでその(自主規制)なんだけど、結局アレっていったいどこでどんな目に遭ってたの?

B: はっはっは。Aさん。そんなこと恐ろしくて答えられるわけないじゃないですか。ここでそれをばらしてしまってはまたどんな目に遭わされることか。

A: むう。それはそうかも知れん。もしかしたら私まで巻き添えを食うかも知れんな。

B: いい加減無駄話で埋めるのはやめてもっと実のある話をせんかいという天の啓示ですよ。しゃきっといきましょう。

A: むむ。次回からは気を引き締めていかねばならんな。じゃあ今日はここまで。

B: ってAさん、テーブルのアレ、西日本限定味のやつですよね! いやあ、前からちょっと気になっていたんですよ。ちょっといただいていいですか? どれどれ。

A: …「しゃきっといきましょう」、ねぇ。(ため息)